@myapihq/cli 1.1.0-wip.5 → 1.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.
Files changed (127) hide show
  1. package/dist/commands/{email.d.ts → audience.d.ts} +2 -0
  2. package/dist/commands/audience.js +245 -0
  3. package/dist/commands/auth.d.ts +5 -2
  4. package/dist/commands/auth.js +133 -5
  5. package/dist/commands/billing.d.ts +2 -0
  6. package/dist/commands/billing.js +18 -1
  7. package/dist/commands/company.d.ts +6 -0
  8. package/dist/commands/company.js +149 -0
  9. package/dist/commands/config.d.ts +2 -0
  10. package/dist/commands/config.js +20 -0
  11. package/dist/commands/crm/companies.d.ts +6 -0
  12. package/dist/commands/crm/companies.js +193 -0
  13. package/dist/commands/crm/contacts.d.ts +6 -0
  14. package/dist/commands/crm/contacts.js +241 -0
  15. package/dist/commands/crm/index.d.ts +6 -0
  16. package/dist/commands/crm/index.js +74 -0
  17. package/dist/commands/database.d.ts +6 -0
  18. package/dist/commands/database.js +259 -0
  19. package/dist/commands/domain.d.ts +3 -0
  20. package/dist/commands/domain.js +67 -1
  21. package/dist/commands/email/campaign.d.ts +2 -0
  22. package/dist/commands/email/campaign.js +34 -1
  23. package/dist/commands/email/index.d.ts +2 -0
  24. package/dist/commands/email/index.js +9 -0
  25. package/dist/commands/email/mailbox.d.ts +2 -0
  26. package/dist/commands/email/mailbox.js +5 -0
  27. package/dist/commands/email/message.d.ts +2 -0
  28. package/dist/commands/email/message.js +8 -0
  29. package/dist/commands/email/template.d.ts +2 -0
  30. package/dist/commands/email/template.js +54 -0
  31. package/dist/commands/email/verify.d.ts +7 -0
  32. package/dist/commands/email/verify.js +52 -0
  33. package/dist/commands/email/warmup.d.ts +2 -0
  34. package/dist/commands/email/warmup.js +7 -0
  35. package/dist/commands/funnel.d.ts +3 -0
  36. package/dist/commands/funnel.js +31 -0
  37. package/dist/commands/image.d.ts +2 -0
  38. package/dist/commands/image.js +43 -9
  39. package/dist/commands/keys.d.ts +2 -0
  40. package/dist/commands/keys.js +5 -0
  41. package/dist/commands/llm.d.ts +6 -0
  42. package/dist/commands/llm.js +156 -0
  43. package/dist/commands/org.d.ts +3 -0
  44. package/dist/commands/org.js +55 -0
  45. package/dist/commands/people.d.ts +6 -0
  46. package/dist/commands/people.js +136 -0
  47. package/dist/commands/pixel.d.ts +11 -3
  48. package/dist/commands/pixel.js +93 -0
  49. package/dist/commands/setup.d.ts +2 -0
  50. package/dist/commands/setup.js +57 -5
  51. package/dist/commands/status.d.ts +7 -0
  52. package/dist/commands/status.js +154 -0
  53. package/dist/commands/storage.d.ts +2 -0
  54. package/dist/commands/storage.js +9 -1
  55. package/dist/commands/update.d.ts +2 -0
  56. package/dist/commands/update.js +3 -0
  57. package/dist/commands/url.d.ts +7 -2
  58. package/dist/commands/url.js +6 -0
  59. package/dist/commands/verify.d.ts +7 -0
  60. package/dist/commands/verify.js +56 -0
  61. package/dist/commands/webhook.d.ts +2 -0
  62. package/dist/commands/webhook.js +31 -6
  63. package/dist/commands/workflow.d.ts +2 -0
  64. package/dist/commands/workflow.js +19 -1
  65. package/dist/completion.d.ts +3 -0
  66. package/dist/completion.js +78 -0
  67. package/dist/config.d.ts +2 -0
  68. package/dist/exposes.d.ts +2 -0
  69. package/dist/exposes.js +13 -0
  70. package/dist/exposes.test.js +99 -0
  71. package/dist/flags.test.js +84 -0
  72. package/dist/index.js +106 -14
  73. package/dist/prompt.d.ts +15 -0
  74. package/dist/prompt.js +27 -0
  75. package/dist/registrant.d.ts +6 -0
  76. package/dist/registrant.js +155 -0
  77. package/dist/services-sync.test.d.ts +1 -0
  78. package/dist/services-sync.test.js +32 -0
  79. package/dist/skills/my-api-hq/SKILL.md +51 -45
  80. package/dist/skills/my-crm-api/README.md +42 -0
  81. package/dist/skills/my-crm-api/SKILL.md +165 -0
  82. package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
  83. package/dist/skills/my-database-api/README.md +41 -0
  84. package/dist/skills/my-database-api/SKILL.md +107 -0
  85. package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
  86. package/dist/skills/my-domain-api/SKILL.md +53 -26
  87. package/dist/skills/my-email-verify-api/README.md +33 -0
  88. package/dist/skills/my-email-verify-api/SKILL.md +91 -0
  89. package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
  90. package/dist/skills/my-funnel-api/SKILL.md +41 -37
  91. package/dist/skills/my-image-api/SKILL.md +48 -44
  92. package/dist/skills/my-llm-api/README.md +35 -0
  93. package/dist/skills/my-llm-api/SKILL.md +125 -0
  94. package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
  95. package/dist/skills/my-storage-api/README.md +1 -1
  96. package/dist/skills/my-storage-api/SKILL.md +44 -54
  97. package/dist/skills/my-webhook-api/README.md +1 -1
  98. package/dist/skills/my-webhook-api/SKILL.md +48 -92
  99. package/dist/skills/my-workflow-api/SKILL.md +46 -115
  100. package/dist/telemetry.d.ts +12 -0
  101. package/dist/telemetry.js +80 -0
  102. package/dist/telemetry.test.d.ts +1 -0
  103. package/dist/telemetry.test.js +101 -0
  104. package/package.json +13 -8
  105. package/dist/commands/email.js +0 -586
  106. package/dist/skills/my-email-api/README.md +0 -45
  107. package/dist/skills/my-email-api/SKILL.md +0 -104
  108. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
  109. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  110. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  111. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  112. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  113. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  114. package/dist/skills/my-image-api/make/.gitkeep +0 -0
  115. package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
  116. package/dist/skills/my-storage-api/make/.gitkeep +0 -0
  117. package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
  118. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  119. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  120. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  121. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  122. package/dist/utils.test.js +0 -48
  123. /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
  124. /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
  125. /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
  126. /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
  127. /package/dist/skills/{my-domain-api/n8n → my-llm-api/openapi}/.gitkeep +0 -0
@@ -1,4 +1,6 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
6
  export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -0,0 +1,245 @@
1
+ import { audience as sdkAudience } from '@myapihq/sdk';
2
+ import { requireConfig } from '../config.js';
3
+ import { success, error, printTable, info, printJson } from '../output.js';
4
+ import { requireOrg, requireArg } from '../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /audience/orgs/{org_id}/audiences',
7
+ 'GET /audience/orgs/{org_id}/audiences',
8
+ 'GET /audience/orgs/{org_id}/audiences/{audience_id}',
9
+ 'PATCH /audience/orgs/{org_id}/audiences/{audience_id}',
10
+ 'DELETE /audience/orgs/{org_id}/audiences/{audience_id}',
11
+ 'GET /audience/orgs/{org_id}/audiences/{audience_id}/members',
12
+ 'POST /audience/orgs/{org_id}/audiences/{audience_id}/refresh',
13
+ ];
14
+ export const SCHEMA = {
15
+ org: 'string',
16
+ name: 'string',
17
+ description: 'string',
18
+ source: 'string',
19
+ filter: 'string',
20
+ limit: 'number',
21
+ offset: 'number',
22
+ };
23
+ function parseFilter(raw) {
24
+ if (typeof raw !== 'string' || raw.trim() === '') {
25
+ error('Missing --filter. Pass a JSON object, e.g.\n --filter \'{"industry":["saas"],"seniority":["vp","c_level"]}\'');
26
+ }
27
+ try {
28
+ const parsed = JSON.parse(raw);
29
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
30
+ error('--filter must be a JSON object (e.g. {"industry":["saas"]}).');
31
+ }
32
+ return parsed;
33
+ }
34
+ catch (e) {
35
+ error(`--filter is not valid JSON: ${e?.message ?? e}`);
36
+ throw e; // unreachable; error() exits
37
+ }
38
+ }
39
+ function summarizeAudience(a) {
40
+ return {
41
+ id: a.id,
42
+ name: a.name,
43
+ source: a.source,
44
+ members: a.member_count,
45
+ created_at: a.created_at,
46
+ };
47
+ }
48
+ async function create(nameArg, flags) {
49
+ const config = requireConfig();
50
+ const orgId = requireOrg(flags, config, 'myapi audience create <name> --source <people|company> --filter <json>');
51
+ const name = nameArg || flags.name;
52
+ if (!name)
53
+ error('Missing required argument <name>.\nUsage: myapi audience create <name> --source <people|company> --filter <json>');
54
+ const source = flags.source;
55
+ if (source !== 'people' && source !== 'company') {
56
+ error(`--source must be "people" or "company" (got: ${source ?? '<missing>'}).`);
57
+ }
58
+ const filter = parseFilter(flags.filter);
59
+ const description = typeof flags.description === 'string' ? flags.description : undefined;
60
+ const res = await sdkAudience.createAudience(config.api_key, orgId, {
61
+ name, source: source, filter, description,
62
+ });
63
+ if (flags.json) {
64
+ printJson(res);
65
+ return;
66
+ }
67
+ success(`Audience created! ID: ${res.id}\nMembers: ${res.member_count}\nSource: ${res.source}`);
68
+ }
69
+ async function list(flags) {
70
+ const config = requireConfig();
71
+ const orgId = requireOrg(flags, config, 'myapi audience list [--org <id>]');
72
+ const res = await sdkAudience.listAudiences(config.api_key, orgId);
73
+ if (flags.json) {
74
+ printJson(res);
75
+ return;
76
+ }
77
+ printTable(res.map(summarizeAudience), {
78
+ flags,
79
+ empty: 'No audiences yet. Create one with: myapi audience create <name> --source <people|company> --filter <json>',
80
+ });
81
+ }
82
+ async function get(id, flags) {
83
+ const config = requireConfig();
84
+ const orgId = requireOrg(flags, config, 'myapi audience get <id> [--org <id>]');
85
+ requireArg(id, 'id', 'myapi audience get <id> [--org <id>]');
86
+ const res = await sdkAudience.getAudience(config.api_key, orgId, id);
87
+ printJson(res);
88
+ }
89
+ async function update(id, flags) {
90
+ const config = requireConfig();
91
+ const orgId = requireOrg(flags, config, 'myapi audience update <id> [--name x] [--description y] [--filter <json>]');
92
+ requireArg(id, 'id', 'myapi audience update <id> [--name x] [--description y] [--filter <json>]');
93
+ const patch = {};
94
+ if (typeof flags.name === 'string')
95
+ patch.name = flags.name;
96
+ if (typeof flags.description === 'string')
97
+ patch.description = flags.description;
98
+ if (typeof flags.filter === 'string')
99
+ patch.filter = parseFilter(flags.filter);
100
+ if (Object.keys(patch).length === 0) {
101
+ error('Pass at least one of --name / --description / --filter to update.');
102
+ }
103
+ const res = await sdkAudience.updateAudience(config.api_key, orgId, id, patch);
104
+ if (flags.json) {
105
+ printJson(res);
106
+ return;
107
+ }
108
+ success(`Audience ${res.id} updated. Members: ${res.member_count}`);
109
+ }
110
+ async function del(id, flags) {
111
+ const config = requireConfig();
112
+ const orgId = requireOrg(flags, config, 'myapi audience delete <id> [--org <id>]');
113
+ requireArg(id, 'id', 'myapi audience delete <id> [--org <id>]');
114
+ await sdkAudience.deleteAudience(config.api_key, orgId, id);
115
+ success(`Audience ${id} deleted`);
116
+ }
117
+ async function members(id, flags) {
118
+ const config = requireConfig();
119
+ const orgId = requireOrg(flags, config, 'myapi audience members <id> [--limit N] [--offset N] [--org <id>]');
120
+ requireArg(id, 'id', 'myapi audience members <id> [--limit N] [--offset N] [--org <id>]');
121
+ const opts = {};
122
+ if (typeof flags.limit === 'number')
123
+ opts.limit = flags.limit;
124
+ if (typeof flags.offset === 'number')
125
+ opts.offset = flags.offset;
126
+ const res = await sdkAudience.getAudienceMembers(config.api_key, orgId, id, opts);
127
+ if (flags.json) {
128
+ printJson(res);
129
+ return;
130
+ }
131
+ // Response doesn't carry `source` — detect by which array is present.
132
+ const isPeople = Array.isArray(res.people);
133
+ const kind = isPeople ? 'people' : 'companies';
134
+ const count = isPeople ? (res.people?.length ?? 0) : (res.companies?.length ?? 0);
135
+ info(`${count} of ${res.total} ${kind}${res.has_more ? ' (more available — pass --offset)' : ''}`);
136
+ if (isPeople) {
137
+ printTable((res.people ?? []).map(p => ({
138
+ id: p.id,
139
+ name: p.full_name,
140
+ email: p.email ?? '',
141
+ domain: p.company?.domain ?? '',
142
+ country: p.location?.country ?? '',
143
+ link_conf: typeof p.link_confidence === 'number' ? p.link_confidence.toFixed(2) : '',
144
+ })), { flags, empty: 'Audience has no members.' });
145
+ }
146
+ else {
147
+ printTable((res.companies ?? []).map(c => ({
148
+ id: c.id,
149
+ domain: c.domain ?? '',
150
+ name: c.name,
151
+ country: c.country ?? '',
152
+ confidence: c.confidence ?? '',
153
+ headcount: c.headcount_lower_bound ?? '',
154
+ sources: c.source_count ?? '',
155
+ })), { flags, empty: 'Audience has no members.' });
156
+ }
157
+ }
158
+ async function refresh(id, flags) {
159
+ const config = requireConfig();
160
+ const orgId = requireOrg(flags, config, 'myapi audience refresh <id> [--org <id>]');
161
+ requireArg(id, 'id', 'myapi audience refresh <id> [--org <id>]');
162
+ const res = await sdkAudience.refreshAudience(config.api_key, orgId, id);
163
+ if (flags.json) {
164
+ printJson(res);
165
+ return;
166
+ }
167
+ const sign = res.delta > 0 ? '+' : '';
168
+ success(`Refreshed. ${res.previous} → ${res.total} (${sign}${res.delta})`);
169
+ }
170
+ const SUBCOMMAND_USAGE = {
171
+ 'create': `myapi audience create <name> --source <people|company> --filter <json> [--description <text>] [--org <id>]
172
+
173
+ The Goldfox filter shape is shared across people/company/audience:
174
+ {
175
+ "confidence": ["high"], // high (default), low, very_low
176
+ "country": ["US", "DE"], // ISO 3166-1 alpha-2
177
+ "country_consistent": true, // TLD+address+phone all agree
178
+ "seniority": ["c_level", "vp_director"], // people-source only
179
+ "email_type": ["corporate"], // people-source only
180
+ "tld_class": ["cctld"], // cctld/generic/vanity/low_trust/other
181
+ "has_c_level": true, // boolean signals
182
+ "has_decision_maker": true,
183
+ "has_careers_page": true,
184
+ "has_investors_page": false,
185
+ "has_shop_page": false,
186
+ "is_registered_entity": true, // recognised legal suffix
187
+ "min_headcount": 5,
188
+ "min_source_count": 3,
189
+ "min_link_confidence": 0.8, // people-source only
190
+ "keyword": "platform", // substring match on domain
191
+ "limit": 100,
192
+ "offset": 0
193
+ }
194
+ Within an array → OR. Across fields → AND. Empty filter = all rows in
195
+ source with default confidence=high (Goldfox's curated tier).
196
+
197
+ Example:
198
+ myapi audience create "EU decision makers" \\
199
+ --source people \\
200
+ --filter '{"seniority":["c_level","vp_director"],"country":["DE","FR","GB"],"email_type":["corporate"]}'`,
201
+ 'list': 'myapi audience list [--org <id>] [--json]',
202
+ 'get': 'myapi audience get <id> [--org <id>]',
203
+ 'update': 'myapi audience update <id> [--name <x>] [--description <y>] [--filter <json>] [--org <id>]',
204
+ 'delete': 'myapi audience delete <id> [--org <id>]',
205
+ 'members': 'myapi audience members <id> [--limit N] [--offset N] [--org <id>] [--json]',
206
+ 'refresh': 'myapi audience refresh <id> [--org <id>]',
207
+ };
208
+ export async function run(subcommand, args, flags) {
209
+ if (!subcommand || (flags.help && !subcommand)) {
210
+ info(`Usage: myapi audience <subcommand>
211
+
212
+ Subcommands:
213
+ create Save a filter as a named audience (people or company source)
214
+ list List all audiences in the org
215
+ get Get a single audience (with its filter + current member_count)
216
+ update Patch name / description / filter (re-evaluates count if filter changes)
217
+ delete Remove an audience
218
+ members Get paged member snapshot (people or companies depending on source)
219
+ refresh Re-evaluate filter against current data; returns delta vs previous
220
+
221
+ All commands accept --org <id> (or set a default: myapi config set-org <id>).
222
+
223
+ Audiences are saved filter snapshots. Same filter shape as myapi people search /
224
+ company search — see "myapi audience create --help" for the schema.`);
225
+ return;
226
+ }
227
+ if (flags.help) {
228
+ const usage = SUBCOMMAND_USAGE[subcommand];
229
+ if (usage)
230
+ info(`Usage: ${usage}`);
231
+ else
232
+ info(`Unknown subcommand: ${subcommand}. Run "myapi audience --help" for the list.`);
233
+ return;
234
+ }
235
+ switch (subcommand) {
236
+ case 'create': return create(args[0], flags);
237
+ case 'list': return list(flags);
238
+ case 'get': return get(args[0], flags);
239
+ case 'update': return update(args[0], flags);
240
+ case 'delete': return del(args[0], flags);
241
+ case 'members': return members(args[0], flags);
242
+ case 'refresh': return refresh(args[0], flags);
243
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi audience --help" for valid subcommands.`);
244
+ }
245
+ }
@@ -1,8 +1,11 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import type { Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
- export declare const HELP = "Usage: myapi auth <subcommand>\n\nSubcommands:\n setup Configure your account\n import-key Import an existing API key non-interactively\n whoami Show current account \u00B7 supports --json\n link [email] Upgrade anonymous account to registered (or add a second session)\n Use myapi auth setup to create a completely new account\n switch [index] Switch active account by index or email\n config Manage CLI defaults (org, funnel, domain) \u00B7 supports set-org / set-funnel / set-domain\n install-skills Install or update the MyAPI skills pack for AI agents\n api-keys Manage API keys \u00B7 list / create / revoke\n keys Alias for api-keys";
5
- export declare const INSTALL_SKILLS_HELP = "Usage: myapi auth install-skills\n\nInstalls the MyAPI skills pack for AI coding agents (Claude, Gemini, Cursor).\n\nThis command writes skill definition files to:\n ~/.agents/skills/myapi/\n\nAnd creates symlinks in the appropriate agent config directories:\n ~/.claude/ (Claude)\n ~/.gemini/ (Gemini)\n ~/.cursor/ (Cursor, if detected)\n\nThese files teach agents how to use the MyAPI CLI and API directly.\nRun this command again to update existing skills to the latest version.";
6
+ export declare const HELP = "Usage: myapi auth <subcommand>\n\nSubcommands:\n setup Configure your account\n import-key Import an existing API key non-interactively\n whoami Show current account \u00B7 supports --json\n link [email] Upgrade anonymous account to registered (or add a second session)\n Use myapi auth setup to create a completely new account\n switch [index] Switch active account by index or email\n config Manage CLI defaults (org, funnel, domain) \u00B7 supports set-org / set-funnel / set-domain\n registrant Manage stored WHOIS contact info for domain registration \u00B7 set / get / clear\n install-skills DEPRECATED \u2014 use `myapi install-skills` instead\n api-keys Manage API keys \u00B7 list / create / revoke\n keys Alias for api-keys";
7
+ export declare const INSTALL_SKILLS_HELP = "Usage: myapi install-skills\n\nInstalls the MyAPI skills pack for AI coding agents (Claude, Gemini, Cursor).\n\nThis command writes skill definition files to:\n ~/.agents/skills/myapi/\n\nAnd creates symlinks in the appropriate agent config directories:\n ~/.claude/ (Claude)\n ~/.gemini/ (Gemini)\n ~/.cursor/ (Cursor, if detected)\n\nThese files teach agents how to use the MyAPI CLI and API directly.\nRun this command again to update existing skills to the latest version.\n\nNote: `myapi auth install-skills` is deprecated and will be removed in the next minor.\nUse `myapi install-skills` going forward.";
6
8
  export declare function link(flags?: Flags, emailArg?: string): Promise<void>;
7
9
  export declare function whoami(flags?: Flags): Promise<void>;
8
10
  export declare function switchCmd(flags?: Flags, indexArg?: string): Promise<void>;
11
+ export declare function registrant(sub: string | undefined, flags?: Flags): Promise<void>;
@@ -1,8 +1,16 @@
1
- import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
1
+ import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts, requireConfig } from '../config.js';
2
2
  import { info, success, error, printJson } from '../output.js';
3
3
  import { ask, confirm, withReadline } from '../prompt.js';
4
4
  import { installSkills } from './setup.js';
5
5
  import { hq } from '@myapihq/sdk';
6
+ import { promptAndSave as promptAndSaveRegistrant } from '../registrant.js';
7
+ export const EXPOSES = [
8
+ 'PATCH /hq/account/upgrade',
9
+ 'POST /hq/account/send-code',
10
+ 'POST /hq/account/verify-code',
11
+ 'GET /hq/billing/balance',
12
+ 'GET /hq/account/free-tier',
13
+ ];
6
14
  export const SCHEMA = {
7
15
  'install-skills': 'boolean',
8
16
  'no-skills': 'boolean',
@@ -19,10 +27,11 @@ Subcommands:
19
27
  Use myapi auth setup to create a completely new account
20
28
  switch [index] Switch active account by index or email
21
29
  config Manage CLI defaults (org, funnel, domain) · supports set-org / set-funnel / set-domain
22
- install-skills Install or update the MyAPI skills pack for AI agents
30
+ registrant Manage stored WHOIS contact info for domain registration · set / get / clear
31
+ install-skills DEPRECATED — use \`myapi install-skills\` instead
23
32
  api-keys Manage API keys · list / create / revoke
24
33
  keys Alias for api-keys`;
25
- export const INSTALL_SKILLS_HELP = `Usage: myapi auth install-skills
34
+ export const INSTALL_SKILLS_HELP = `Usage: myapi install-skills
26
35
 
27
36
  Installs the MyAPI skills pack for AI coding agents (Claude, Gemini, Cursor).
28
37
 
@@ -35,7 +44,10 @@ And creates symlinks in the appropriate agent config directories:
35
44
  ~/.cursor/ (Cursor, if detected)
36
45
 
37
46
  These files teach agents how to use the MyAPI CLI and API directly.
38
- Run this command again to update existing skills to the latest version.`;
47
+ Run this command again to update existing skills to the latest version.
48
+
49
+ Note: \`myapi auth install-skills\` is deprecated and will be removed in the next minor.
50
+ Use \`myapi install-skills\` going forward.`;
39
51
  const LINK_HELP = `Usage: myapi auth link [email]
40
52
 
41
53
  Behavior depends on whether the email is new or already registered:
@@ -147,10 +159,30 @@ export async function whoami(flags = {}) {
147
159
  if (!config?.api_key)
148
160
  error('Not configured. Run: myapi auth setup');
149
161
  let balance = null;
162
+ // Backend returns an array of per-service entries, or null. The SDK type
163
+ // used to lie about this (claimed scalar) — see hq.ts FreeTierEntry.
164
+ let freeTier = null;
165
+ // Note any auth-rejection from the balance probe so we don't silently
166
+ // print stale cached config when the key has actually been invalidated
167
+ // server-side. Other failures (endpoint missing, transient 500s) stay
168
+ // swallowed — balance is best-effort, not a key-validation primitive.
169
+ let keyRejected = false;
150
170
  try {
151
171
  balance = await hq.getBalance(config.api_key);
152
172
  }
153
- catch { }
173
+ catch (err) {
174
+ if (err?.status === 401 || /invalid api key/i.test(err?.code ?? ''))
175
+ keyRejected = true;
176
+ }
177
+ // Free-tier query is best-effort — backend may not have it for every
178
+ // account type, and surfacing nothing is better than failing whoami.
179
+ try {
180
+ freeTier = await hq.getFreeTier(config.api_key);
181
+ }
182
+ catch (err) {
183
+ if (err?.status === 401 || /invalid api key/i.test(err?.code ?? ''))
184
+ keyRejected = true;
185
+ }
154
186
  if (flags.json) {
155
187
  printJson({
156
188
  email: config.email ?? null,
@@ -160,9 +192,16 @@ export async function whoami(flags = {}) {
160
192
  is_anonymous: config.is_anonymous ?? false,
161
193
  balance: balance?.balance_display ?? null,
162
194
  credits: balance?.credits_display ?? null,
195
+ free_tier: freeTier ?? null,
196
+ key_rejected: keyRejected,
163
197
  });
164
198
  return;
165
199
  }
200
+ if (keyRejected) {
201
+ info('⚠ The backend rejected your API key. The values below are from local config and may not match server state.');
202
+ info(' Run: myapi auth setup');
203
+ info('');
204
+ }
166
205
  if (config.email)
167
206
  info(`Email: ${config.email}`);
168
207
  info(`Account: ${config.account_id}`);
@@ -170,6 +209,13 @@ export async function whoami(flags = {}) {
170
209
  info(`Funnel: ${config.default_funnel ?? '(none)'}`);
171
210
  info(`Type: ${config.is_anonymous ? 'anonymous' : 'registered'}`);
172
211
  info(`Balance: ${balance ? `${balance.balance_display} | Credits: ${balance.credits_display} (not usable for domains)` : '(unavailable)'}`);
212
+ if (Array.isArray(freeTier) && freeTier.length > 0) {
213
+ const parts = freeTier
214
+ .filter((e) => e && typeof e.service === 'string')
215
+ .map((e) => `${e.service} ${e.used ?? 0}/${e.allowance ?? '?'}`);
216
+ if (parts.length > 0)
217
+ info(`FreeTier: ${parts.join(' · ')}`);
218
+ }
173
219
  }
174
220
  // myapi auth switch [index] — switch between saved accounts.
175
221
  export async function switchCmd(flags = {}, indexArg) {
@@ -215,3 +261,85 @@ export async function switchCmd(flags = {}, indexArg) {
215
261
  success(`› Switched to ${a.email ?? a.account_id}`);
216
262
  }
217
263
  }
264
+ // ── myapi auth registrant <set|get|clear> ─────────────────────────────────
265
+ //
266
+ // Manages the locally-stored WHOIS registrant info that `myapi domain
267
+ // register` injects into every call. `set` is the one-time interactive
268
+ // setup; `--registrant-json` is the agent-friendly bypass. `get` and
269
+ // `clear` are housekeeping.
270
+ const REGISTRANT_HELP = `Usage: myapi auth registrant <set|get|clear>
271
+
272
+ Manages the locally-stored WHOIS contact info that \`myapi domain register\`
273
+ requires (ICANN-mandated). Stored in ~/.myapi/config.json under the active
274
+ account; never synced to the backend except as a per-request field.
275
+
276
+ Subcommands:
277
+ set Interactive prompt for all fields. Or pass --registrant-json
278
+ '<json>' to set non-interactively (agent-friendly).
279
+ get Print the stored registrant (or "not set").
280
+ clear Remove the stored registrant from the active account.`;
281
+ export async function registrant(sub, flags = {}) {
282
+ if (flags.help || !sub) {
283
+ info(REGISTRANT_HELP);
284
+ return;
285
+ }
286
+ const config = requireConfig();
287
+ switch (sub) {
288
+ case 'set': {
289
+ // Agent path: --registrant-json '<inline>' writes directly without prompting.
290
+ const inlineJson = typeof flags['registrant-json'] === 'string' ? flags['registrant-json'] : null;
291
+ if (inlineJson) {
292
+ let parsed;
293
+ try {
294
+ parsed = JSON.parse(inlineJson);
295
+ }
296
+ catch (e) {
297
+ error(`--registrant-json is not valid JSON: ${e.message}`);
298
+ }
299
+ const required = ['name', 'email', 'phone', 'street', 'city', 'postal_code', 'country_code'];
300
+ const missing = required.filter(k => !parsed[k]);
301
+ if (missing.length > 0)
302
+ error(`Missing required field(s): ${missing.join(', ')}`);
303
+ saveConfig({ ...config, registrant: parsed });
304
+ success('Saved registrant from --registrant-json.');
305
+ return;
306
+ }
307
+ const r = await promptAndSaveRegistrant(config, config.registrant);
308
+ success(`Saved. ${r.name} · ${r.email} · ${r.country_code}`);
309
+ return;
310
+ }
311
+ case 'get': {
312
+ if (!config.registrant) {
313
+ info('No registrant stored. Set one with: myapi auth registrant set');
314
+ return;
315
+ }
316
+ if (flags.json) {
317
+ printJson(config.registrant);
318
+ return;
319
+ }
320
+ const r = config.registrant;
321
+ info(`Name: ${r.name}`);
322
+ info(`Email: ${r.email}`);
323
+ info(`Phone: ${r.phone}`);
324
+ info(`Street: ${r.street}`);
325
+ info(`City: ${r.city}`);
326
+ if (r.state)
327
+ info(`State: ${r.state}`);
328
+ info(`Postal code: ${r.postal_code}`);
329
+ info(`Country: ${r.country_code}`);
330
+ return;
331
+ }
332
+ case 'clear': {
333
+ if (!config.registrant) {
334
+ info('No registrant stored — nothing to clear.');
335
+ return;
336
+ }
337
+ const { registrant: _drop, ...rest } = config;
338
+ saveConfig(rest);
339
+ success('Cleared.');
340
+ return;
341
+ }
342
+ default:
343
+ error(`Unknown subcommand: ${sub}. Run \`myapi auth registrant --help\` for the list.`);
344
+ }
345
+ }
@@ -1,6 +1,8 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import type { Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
3
4
  export declare const SCHEMA: FlagSchema;
5
+ export declare const EXPOSES: Exposes;
4
6
  export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
5
7
  export declare function balance(flags: Flags): Promise<void>;
6
8
  export declare function history(flags: Flags): Promise<void>;
@@ -1,9 +1,15 @@
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 { confirm } from '../prompt.js';
4
+ import { confirm, isNonInteractive } from '../prompt.js';
5
5
  import { formatDate } from '../utils.js';
6
6
  export const SCHEMA = {};
7
+ export const EXPOSES = [
8
+ 'GET /hq/billing/balance',
9
+ 'GET /hq/billing/history',
10
+ 'POST /hq/billing/setup-payment',
11
+ 'POST /hq/billing/topup',
12
+ ];
7
13
  const SUBCOMMAND_USAGE = {
8
14
  'balance': 'myapi billing balance [--json]',
9
15
  'history': 'myapi billing history [--json]',
@@ -57,6 +63,11 @@ export async function balance(flags) {
57
63
  const accountType = config.is_anonymous ? 'anonymous' : `registered (${config.email ?? ''})`;
58
64
  info(`Account: ${accountType}`);
59
65
  info(`Balance: ${result.balance_display} | Credits: ${result.credits_display} (not usable for domains) | Payment method: ${pm}`);
66
+ // Anonymous accounts can't transact — paid surface is gated on a
67
+ // verified email and credits only fund on upgrade. Surface the unblock.
68
+ if (config.is_anonymous) {
69
+ info('→ Link an email to unlock $5 free credit + paid actions: myapi auth link <email>');
70
+ }
60
71
  }
61
72
  export async function history(flags) {
62
73
  const config = requireConfig();
@@ -82,6 +93,12 @@ export async function topup(amountStr, flags) {
82
93
  error('Amount must be a positive whole number of dollars (e.g. myapi billing topup 10)');
83
94
  }
84
95
  if (!flags.yes && !flags.y && amount >= 50) {
96
+ // Refuse loudly in non-interactive contexts (agent harnesses, CI) instead
97
+ // of either hanging on a prompt no human will answer, or silently
98
+ // auto-cancelling with no actionable error. Force the caller to pass --yes.
99
+ if (isNonInteractive()) {
100
+ error(`Charge of $${amount} requires confirmation. Re-run with --yes:\n myapi billing topup ${amount} --yes`);
101
+ }
85
102
  const ok = await confirm(`› Charge $${amount} to your saved payment method? (y/N) `, false);
86
103
  if (!ok) {
87
104
  info('Cancelled.');
@@ -0,0 +1,6 @@
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 run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;