@myapihq/cli 1.1.0-wip.4 → 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 +14 -2
  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
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { error, info, success } from './output.js';
2
+ import { error, info, success, banner } from './output.js';
3
3
  import { loadConfig } from './config.js';
4
4
  import { MyApiError } from '@myapihq/sdk';
5
5
  import * as fs from 'fs';
@@ -18,8 +18,18 @@ import * as workflowCmd from './commands/workflow.js';
18
18
  import * as emailCmd from './commands/email/index.js';
19
19
  import * as imageCmd from './commands/image.js';
20
20
  import * as storageCmd from './commands/storage.js';
21
+ import * as pixelCmd from './commands/pixel.js';
22
+ import * as urlCmd from './commands/url.js';
21
23
  import * as authCmd from './commands/auth.js';
22
24
  import * as configCmd from './commands/config.js';
25
+ import * as statusCmd from './commands/status.js';
26
+ import * as peopleCmd from './commands/people.js';
27
+ import * as companyCmd from './commands/company.js';
28
+ import * as audienceCmd from './commands/audience.js';
29
+ import * as llmCmd from './commands/llm.js';
30
+ import * as databaseCmd from './commands/database.js';
31
+ import * as crmCmd from './commands/crm/index.js';
32
+ import { initCompletion, installCompletion, uninstallCompletion } from './completion.js';
23
33
  // Each command file declares the value flags it understands. We union them
24
34
  // into a single schema for the upfront parse, so adding a new value flag in
25
35
  // one command means editing one file (its SCHEMA), not a global allowlist.
@@ -33,7 +43,16 @@ const COMBINED_SCHEMA = {
33
43
  ...imageCmd.SCHEMA,
34
44
  ...keysCmd.SCHEMA,
35
45
  ...orgCmd.SCHEMA,
46
+ ...pixelCmd.SCHEMA,
47
+ ...statusCmd.SCHEMA,
48
+ ...peopleCmd.SCHEMA,
49
+ ...companyCmd.SCHEMA,
50
+ ...audienceCmd.SCHEMA,
51
+ ...llmCmd.SCHEMA,
52
+ ...databaseCmd.SCHEMA,
53
+ ...crmCmd.SCHEMA,
36
54
  ...storageCmd.SCHEMA,
55
+ ...urlCmd.SCHEMA,
37
56
  ...webhookCmd.SCHEMA,
38
57
  ...workflowCmd.SCHEMA,
39
58
  // Top-level flags
@@ -62,6 +81,7 @@ const ERROR_MESSAGES = {
62
81
  RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
63
82
  INSUFFICIENT_BALANCE: 'Insufficient balance. Run: myapi billing topup <amount>',
64
83
  INVALID_AMOUNT: 'Amount out of range. Maximum single top-up is $100. Run: myapi billing topup <amount>',
84
+ SERVICE_NOT_LAUNCHED: 'This service is disabled pre-launch. Track availability via: myapi status',
65
85
  // invalid_json_response intentionally absent — the SDK's MyApiError now
66
86
  // builds a useful detailed message for that case (status + URL + body
67
87
  // snippet), and friendlyError(err.code) would override it.
@@ -70,6 +90,9 @@ function friendlyError(code) {
70
90
  return ERROR_MESSAGES[code] || code;
71
91
  }
72
92
  async function main() {
93
+ // Shell autocomplete: if invoked by the shell with completion env vars,
94
+ // omelette short-circuits before normal dispatch. Always cheap on a real run.
95
+ initCompletion();
73
96
  const { args, flags } = parseFlags(process.argv.slice(2), COMBINED_SCHEMA);
74
97
  if (flags.version || flags.v || flags.V) {
75
98
  const latest = await updateCmd.latestVersion();
@@ -130,6 +153,30 @@ async function main() {
130
153
  case 'storage':
131
154
  await storageCmd.run(subcommand, restArgs, flags);
132
155
  break;
156
+ case 'pixel':
157
+ await pixelCmd.run(subcommand, restArgs, flags);
158
+ break;
159
+ case 'people':
160
+ await peopleCmd.run(subcommand, restArgs, flags);
161
+ break;
162
+ case 'company':
163
+ await companyCmd.run(subcommand, restArgs, flags);
164
+ break;
165
+ case 'audience':
166
+ await audienceCmd.run(subcommand, restArgs, flags);
167
+ break;
168
+ case 'llm':
169
+ await llmCmd.run(subcommand, restArgs, flags);
170
+ break;
171
+ case 'database':
172
+ await databaseCmd.run(subcommand, restArgs, flags);
173
+ break;
174
+ case 'crm':
175
+ await crmCmd.run(subcommand, restArgs, flags);
176
+ break;
177
+ case 'url':
178
+ await urlCmd.run(subcommand, restArgs, flags);
179
+ break;
133
180
  // Convenience aliases
134
181
  case 'setup':
135
182
  await setupCmd.setup(flags);
@@ -137,6 +184,9 @@ async function main() {
137
184
  case 'whoami':
138
185
  await authCmd.whoami(flags);
139
186
  break;
187
+ case 'status':
188
+ await statusCmd.run(subcommand, restArgs, flags);
189
+ break;
140
190
  case 'keys':
141
191
  await keysCmd.run(subcommand, restArgs, flags);
142
192
  break;
@@ -151,6 +201,20 @@ async function main() {
151
201
  await setupCmd.installSkills();
152
202
  success('› Skills installed.');
153
203
  break;
204
+ case 'completion':
205
+ // Order matters: omelette's setup/cleanup helpers call process.exit()
206
+ // unconditionally, so anything we want to show has to print first.
207
+ if (subcommand === 'uninstall') {
208
+ success('› Removing completion from shell init file…');
209
+ info(' Restart your shell (or `source ~/.bashrc` / `~/.zshrc`) to take effect.');
210
+ uninstallCompletion();
211
+ }
212
+ else {
213
+ success('› Installing tab completion…');
214
+ info(' Restart your shell (or `source ~/.bashrc` / `~/.zshrc`) to enable.');
215
+ installCompletion();
216
+ }
217
+ break;
154
218
  case 'help':
155
219
  await dispatchHelp(subcommand);
156
220
  break;
@@ -167,8 +231,16 @@ async function main() {
167
231
  error('A verified email is required. Run: myapi auth link');
168
232
  else if (err.code === 'NO_PAYMENT_METHOD')
169
233
  error('No payment method on file. Run: myapi billing setup');
170
- else
234
+ else {
235
+ // Anonymous accounts can't top up (no payment surface). The
236
+ // natural unblock is to link an email — the upgrade grants the
237
+ // free credit and opens the paid surface.
238
+ const cfg = loadConfig();
239
+ if (cfg?.is_anonymous) {
240
+ error('Insufficient balance. Anonymous accounts have no free credit — link an email to unlock $5: myapi auth link <email>');
241
+ }
171
242
  error('Insufficient balance. Run: myapi billing topup <amount>');
243
+ }
172
244
  }
173
245
  else
174
246
  error(friendlyError(err.code) || err.message);
@@ -193,6 +265,9 @@ async function dispatchAuth(subcommand, restArgs, flags) {
193
265
  case 'link': return authCmd.link(flags, restArgs[0]);
194
266
  case 'switch': return authCmd.switchCmd(flags, restArgs[0]);
195
267
  case 'install-skills':
268
+ // DEPRECATED: install-skills is now a top-level command. The auth subcommand
269
+ // form is kept for one minor version with a warning, then will be removed.
270
+ banner('› Note: `myapi auth install-skills` is deprecated. Use `myapi install-skills` instead.');
196
271
  if (flags.help) {
197
272
  info(authCmd.INSTALL_SKILLS_HELP);
198
273
  return;
@@ -201,6 +276,7 @@ async function dispatchAuth(subcommand, restArgs, flags) {
201
276
  success('› Skills installed.');
202
277
  return;
203
278
  case 'config': return configCmd.run(restArgs[0], restArgs.slice(1), flags);
279
+ case 'registrant': return authCmd.registrant(restArgs[0], flags);
204
280
  case 'api-keys':
205
281
  case 'keys': return keysCmd.runApiKeys(restArgs[0], restArgs.slice(1), flags);
206
282
  default: info('Unknown subcommand. Run: myapi auth --help');
@@ -214,6 +290,13 @@ const HELP_TARGETS = {
214
290
  email: f => emailCmd.run(undefined, [], f),
215
291
  image: f => imageCmd.run(undefined, [], f),
216
292
  storage: f => storageCmd.run(undefined, [], f),
293
+ status: f => statusCmd.run(undefined, [], f),
294
+ people: f => peopleCmd.run(undefined, [], f),
295
+ company: f => companyCmd.run(undefined, [], f),
296
+ audience: f => audienceCmd.run(undefined, [], f),
297
+ llm: f => llmCmd.run(undefined, [], f),
298
+ database: f => databaseCmd.run(undefined, [], f),
299
+ crm: f => crmCmd.run(undefined, [], f),
217
300
  org: f => orgCmd.run(undefined, [], f),
218
301
  billing: f => billingCmd.run(undefined, [], f),
219
302
  keys: f => keysCmd.run(undefined, [], f),
@@ -247,24 +330,33 @@ Usage: myapi <command> [subcommand] [args]
247
330
  myapi --version
248
331
 
249
332
  Commands:
250
- auth Manage account · setup · whoami · link
251
- billing Check balance and manage billing
252
- org Manage organizations (tip: myapi org create "name" --yes to auto-set as default)
253
- update Update CLI and skills to the latest version
254
- domain Manage domain configurations
255
- funnel Manage websites (publish pages, custom domains, funnels)
256
- webhook Manage inbound webhook endpoints and inspect deliveries
257
- email Manage mailboxes, send/read email, templates, and campaigns
258
- workflow Run actions (send email, post to Slack) when a webhook fires
259
- image Generate AI images and manage them in storage
260
- storage Upload, ingest, list, and serve assets from edge storage
333
+ auth Manage account · setup · whoami · link
334
+ status Single-screen view of account + resources in the default org
335
+ install-skills Install or update the MyAPI skills pack for AI agents
336
+ billing Check balance and manage billing
337
+ org Manage organizations (tip: myapi org create "name" --yes to auto-set as default)
338
+ update Update CLI and skills to the latest version
339
+ domain Manage domain configurations
340
+ funnel Manage websites (publish pages, custom domains, funnels)
341
+ webhook Manage inbound webhook endpoints and inspect deliveries
342
+ email Manage mailboxes, send/read email, templates, and campaigns
343
+ workflow Run actions (send email, post to Slack) when a webhook fires
344
+ image Generate AI images and manage them in storage
345
+ storage Upload, ingest, list, and serve assets from edge storage
346
+ pixel Read pixel analytics: visits, events, identity resolution
347
+ people Search the contact database (filter by industry, seniority, country, ...)
348
+ company Search the company database (filter by industry, size, country, ...)
349
+ audience Save filter snapshots as named audiences (people or companies)
350
+ llm Run LLM completions and embeddings (chat + embed, with usage/cost)
351
+ database KV store with namespaces + CAS — the substrate for stateful agent apps
352
+ crm Canonical store of engaged contacts + companies, with auto-ingest from webhooks
353
+ url Shorten URLs to compact myurlto.com links
261
354
 
262
355
  Aliases:
263
356
  whoami → myapi auth whoami
264
357
  keys → myapi auth api-keys
265
358
  setup → myapi auth setup
266
359
  config → myapi auth config
267
- install-skills → myapi auth install-skills
268
360
 
269
361
  Run "myapi <command> --help" for subcommand help.
270
362
 
package/dist/prompt.d.ts CHANGED
@@ -1,4 +1,19 @@
1
1
  import * as readline from 'readline';
2
+ /**
3
+ * True when no human is available to answer prompts.
4
+ *
5
+ * Detects three cases:
6
+ * - Piped / redirected stdin (most CI runners, shell pipes)
7
+ * - `CI=true` (GitHub Actions, GitLab CI, CircleCI, etc.)
8
+ * - `MYAPI_NON_INTERACTIVE=1` (explicit opt-out, used by agent harnesses
9
+ * that allocate a PTY for output but have no human at the keyboard)
10
+ *
11
+ * Some agent test-harnesses allocate a PTY for the child process so its
12
+ * coloured output renders correctly. In that case `process.stdin.isTTY`
13
+ * is true but there's no human to type 'y'/'n' — the prompt would hang
14
+ * until timeout. The env-var escape hatches handle that case.
15
+ */
16
+ export declare function isNonInteractive(): boolean;
2
17
  /**
3
18
  * Ask a free-form question on stdin. Returns the trimmed answer.
4
19
  *
package/dist/prompt.js CHANGED
@@ -1,4 +1,23 @@
1
1
  import * as readline from 'readline';
2
+ /**
3
+ * True when no human is available to answer prompts.
4
+ *
5
+ * Detects three cases:
6
+ * - Piped / redirected stdin (most CI runners, shell pipes)
7
+ * - `CI=true` (GitHub Actions, GitLab CI, CircleCI, etc.)
8
+ * - `MYAPI_NON_INTERACTIVE=1` (explicit opt-out, used by agent harnesses
9
+ * that allocate a PTY for output but have no human at the keyboard)
10
+ *
11
+ * Some agent test-harnesses allocate a PTY for the child process so its
12
+ * coloured output renders correctly. In that case `process.stdin.isTTY`
13
+ * is true but there's no human to type 'y'/'n' — the prompt would hang
14
+ * until timeout. The env-var escape hatches handle that case.
15
+ */
16
+ export function isNonInteractive() {
17
+ return (!process.stdin.isTTY ||
18
+ process.env.CI === 'true' ||
19
+ process.env.MYAPI_NON_INTERACTIVE === '1');
20
+ }
2
21
  /**
3
22
  * Ask a free-form question on stdin. Returns the trimmed answer.
4
23
  *
@@ -6,6 +25,14 @@ import * as readline from 'readline';
6
25
  * inline. Replaces five copies of the same boilerplate across commands.
7
26
  */
8
27
  export async function ask(question) {
28
+ // Non-interactive contexts (CI, agent subshells, piped stdin) used to
29
+ // hang here forever waiting for input that never arrives. Return an
30
+ // empty answer immediately so callers can fall back to their default.
31
+ if (isNonInteractive()) {
32
+ process.stdout.write(question);
33
+ process.stdout.write('\n');
34
+ return '';
35
+ }
9
36
  return withReadline(rl => new Promise(resolve => {
10
37
  rl.question(question, ans => resolve(ans.trim()));
11
38
  }));
@@ -0,0 +1,6 @@
1
+ import type { domain as sdkDomain } from '@myapihq/sdk';
2
+ import { type Config } from './config.js';
3
+ import type { Flags } from './helpers.js';
4
+ export type Registrant = sdkDomain.Registrant;
5
+ export declare function resolveRegistrantForRegister(flags: Flags, config: Config): Promise<Registrant>;
6
+ export declare function promptAndSave(config: Config, prefill?: Partial<Registrant>): Promise<Registrant>;
@@ -0,0 +1,155 @@
1
+ // Resolution + prompting helpers for the ICANN WHOIS registrant required
2
+ // at `myapi domain register` time.
3
+ //
4
+ // Sources, in priority order:
5
+ // 1. --registrant-json '<inline JSON>'
6
+ // 2. Per-field flags (--registrant-name, --registrant-email, …)
7
+ // 3. Stored on the active account (set via `myapi auth registrant set`)
8
+ // 4. Interactive TTY prompt — offers to save to config
9
+ // 5. Hard error in non-interactive contexts
10
+ //
11
+ // Validation is intentionally light. We trust the backend to enforce
12
+ // ICANN rules (E.164 phone, ISO country code, state-required-for-US/CA).
13
+ // Local validation here would silently diverge from server-side as rules
14
+ // evolve. We only catch obvious typos (empty required fields, invalid
15
+ // country length).
16
+ import { error } from './output.js';
17
+ import { ask, confirm, isNonInteractive } from './prompt.js';
18
+ import { saveConfig } from './config.js';
19
+ // ── Flag-based extraction ──────────────────────────────────────────────────
20
+ // Returns a partial registrant from per-field flags. Caller decides
21
+ // whether the partial is "complete enough" (we require all six base
22
+ // fields plus country_code; state is optional).
23
+ function fromFlags(flags) {
24
+ return {
25
+ name: typeof flags['registrant-name'] === 'string' ? flags['registrant-name'] : undefined,
26
+ email: typeof flags['registrant-email'] === 'string' ? flags['registrant-email'] : undefined,
27
+ phone: typeof flags['registrant-phone'] === 'string' ? flags['registrant-phone'] : undefined,
28
+ street: typeof flags['registrant-street'] === 'string' ? flags['registrant-street'] : undefined,
29
+ city: typeof flags['registrant-city'] === 'string' ? flags['registrant-city'] : undefined,
30
+ state: typeof flags['registrant-state'] === 'string' ? flags['registrant-state'] : undefined,
31
+ postal_code: typeof flags['registrant-postal-code'] === 'string' ? flags['registrant-postal-code'] : undefined,
32
+ country_code: typeof flags['registrant-country'] === 'string' ? flags['registrant-country'] : undefined,
33
+ organization: typeof flags['registrant-organization'] === 'string' ? flags['registrant-organization'] : undefined,
34
+ };
35
+ }
36
+ function fromJson(raw) {
37
+ try {
38
+ return JSON.parse(raw);
39
+ }
40
+ catch (e) {
41
+ error(`--registrant-json is not valid JSON: ${e.message}`);
42
+ }
43
+ }
44
+ // Merge sources, later sources win on a per-field basis. Empty strings are
45
+ // ignored so a partial flag set doesn't clobber config values.
46
+ function merge(...sources) {
47
+ const out = {};
48
+ for (const src of sources) {
49
+ if (!src)
50
+ continue;
51
+ for (const [k, v] of Object.entries(src)) {
52
+ if (v !== undefined && v !== '')
53
+ out[k] = v;
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+ // Required fields (state is conditional — backend validates US/CA specifically).
59
+ const REQUIRED = ['name', 'email', 'phone', 'street', 'city', 'postal_code', 'country_code'];
60
+ function missingFields(r) {
61
+ return REQUIRED.filter(k => !r[k]);
62
+ }
63
+ function validate(r) {
64
+ if (r.country_code.length !== 2) {
65
+ return `country_code "${r.country_code}" must be ISO 3166-1 alpha-2 (e.g. "US", "DE", "FR")`;
66
+ }
67
+ if ((r.country_code === 'US' || r.country_code === 'CA') && !r.state) {
68
+ return `state is required for US and CA registrants`;
69
+ }
70
+ return null;
71
+ }
72
+ // ── Interactive prompt ─────────────────────────────────────────────────────
73
+ const FIELDS = [
74
+ { key: 'name', label: 'Full name', required: true },
75
+ { key: 'email', label: 'Email', required: true },
76
+ { key: 'phone', label: 'Phone (E.164, e.g. +33612345678)', required: true },
77
+ { key: 'street', label: 'Street', required: true },
78
+ { key: 'city', label: 'City', required: true },
79
+ { key: 'postal_code', label: 'Postal code', required: true },
80
+ { key: 'country_code', label: 'Country code (ISO alpha-2, e.g. "US")', required: true },
81
+ { key: 'state', label: 'State (US/CA only — press Enter to skip)', required: false },
82
+ { key: 'organization', label: 'Organization (optional — press Enter to skip)', required: false },
83
+ ];
84
+ // Walk the field list interactively. `existing` fills the default for each
85
+ // prompt (Enter accepts) so the user can re-confirm without retyping.
86
+ async function promptRegistrant(existing = {}) {
87
+ const out = { ...existing };
88
+ for (const f of FIELDS) {
89
+ const current = out[f.key];
90
+ const suffix = current ? ` [${current}]` : '';
91
+ let ans = await ask(` ${f.label}${suffix}: `);
92
+ if (!ans && current)
93
+ ans = String(current);
94
+ if (!ans && f.required) {
95
+ error(`${f.key} is required.`);
96
+ }
97
+ if (ans)
98
+ out[f.key] = ans.trim();
99
+ }
100
+ // Validate the assembled record; bail with a clear message on common issues.
101
+ const reason = validate(out);
102
+ if (reason)
103
+ error(reason);
104
+ return out;
105
+ }
106
+ // ── Public resolver — called by `myapi domain register` ────────────────────
107
+ export async function resolveRegistrantForRegister(flags, config) {
108
+ // 1 + 2: flags
109
+ const jsonSource = typeof flags['registrant-json'] === 'string' ? fromJson(flags['registrant-json']) : undefined;
110
+ const flagSource = fromFlags(flags);
111
+ // 3: stored config
112
+ const stored = config.registrant;
113
+ // Merge: stored < json < per-flag (later wins).
114
+ const merged = merge(stored, jsonSource, flagSource);
115
+ const missing = missingFields(merged);
116
+ if (missing.length === 0) {
117
+ const reason = validate(merged);
118
+ if (reason)
119
+ error(reason);
120
+ return merged;
121
+ }
122
+ // 4: TTY prompt if available.
123
+ if (!isNonInteractive()) {
124
+ console.error('› Domain registration requires WHOIS contact info (ICANN). Filling in missing fields:');
125
+ const r = await promptRegistrant(merged);
126
+ if (!stored) {
127
+ const save = await confirm(' Save as your default for future domain registrations? (Y/n) ', true);
128
+ if (save) {
129
+ saveConfig({ ...config, registrant: r });
130
+ console.error(' ✓ Saved. Manage with: myapi auth registrant <get|clear>');
131
+ }
132
+ }
133
+ return r;
134
+ }
135
+ // 5: hard error.
136
+ error(`Missing registrant field(s): ${missing.join(', ')}\n\n` +
137
+ `Provide them via one of:\n` +
138
+ ` myapi auth registrant set (one-time interactive setup)\n` +
139
+ ` --registrant-json '{"name":"...","email":"...","phone":"...","street":"...","city":"...","postal_code":"...","country_code":"DE"}'\n` +
140
+ ` Per-field flags: --registrant-name, --registrant-email, --registrant-phone,\n` +
141
+ ` --registrant-street, --registrant-city, --registrant-postal-code,\n` +
142
+ ` --registrant-country, --registrant-state (US/CA only)`);
143
+ }
144
+ // ── Used by `myapi auth registrant set` ────────────────────────────────────
145
+ // Public so the auth subcommand can drive the same prompt sequence without
146
+ // going through the register flow.
147
+ export async function promptAndSave(config, prefill = {}) {
148
+ if (isNonInteractive()) {
149
+ error('myapi auth registrant set requires a TTY. Use --json \'{...}\' instead.');
150
+ }
151
+ console.error('› Setting your WHOIS registrant info (used at every domain register call):');
152
+ const r = await promptRegistrant(prefill);
153
+ saveConfig({ ...config, registrant: r });
154
+ return r;
155
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ // CI enforcement that every skills/<name>/claude/.claude-plugin/plugin.json
2
+ // is regenerable from packages/sdk/src/services.ts without drift.
3
+ //
4
+ // If this test fails, run from the repo root:
5
+ // node scripts/canonical-sync.mjs
6
+ //
7
+ // That regenerates the affected plugin.json files; commit and the test
8
+ // passes again.
9
+ import { describe, it, expect } from 'vitest';
10
+ import { execFileSync } from 'node:child_process';
11
+ import * as path from 'node:path';
12
+ import * as url from 'node:url';
13
+ const __filename = url.fileURLToPath(import.meta.url);
14
+ const REPO_ROOT = path.resolve(path.dirname(__filename), '..', '..', '..');
15
+ describe('canonical-sync: services.ts → skills/*/plugin.json', () => {
16
+ it('every plugin.json matches its services.ts entry', () => {
17
+ let out = '';
18
+ let exitCode = 0;
19
+ try {
20
+ out = execFileSync('node', ['scripts/canonical-sync.mjs', '--check'], {
21
+ cwd: REPO_ROOT,
22
+ encoding: 'utf-8',
23
+ stdio: ['ignore', 'pipe', 'pipe'],
24
+ });
25
+ }
26
+ catch (err) {
27
+ exitCode = err.status ?? 1;
28
+ out = (err.stdout?.toString() ?? '') + (err.stderr?.toString() ?? '');
29
+ }
30
+ expect(exitCode, `canonical-sync --check failed (exit ${exitCode}). Output:\n${out}\n\nRun \`node scripts/canonical-sync.mjs\` from the repo root to regenerate the affected plugin.json files, then commit.`).toBe(0);
31
+ });
32
+ });
@@ -1,14 +1,19 @@
1
1
  ---
2
2
  name: my-api-hq
3
+ version: 1.0.0
3
4
  description: >
4
5
  Auth, organizations, and billing hub. Start here to get an api_key and org_id — every other service depends on both.
6
+ triggers: [api key, account, organization, org, billing, balance, topup, credits, setup, defaults, brand, sync brand]
7
+ checksum: sha256-pending
5
8
  ---
6
9
 
7
10
  # MyApiHQ
8
11
 
9
12
  The root service. It manages accounts, API keys, organizations, and billing. No other service works without credentials from here.
10
13
 
11
- ## How It Fits Together
14
+ ## Capabilities
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 auth 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.
12
17
 
13
18
  ```
14
19
  myapihq ──► org_id + api_key
@@ -18,65 +23,66 @@ myapihq ──► org_id + api_key
18
23
  (domains) (websites)
19
24
  ```
20
25
 
21
- - **mydomainapi** needs an `org_id` to register or manage domains.
22
- - **myfunnelapi** needs an `org_id` to create funnels; a registered domain makes the funnel live on your custom URL.
26
+ ### Anonymous vs registered accounts
23
27
 
24
- ## Quick Start (CLI)
28
+ Two tiers, chosen at setup time:
25
29
 
26
- ```bash
27
- myapi auth setup # interactive setup: creates account, generates api_key, sets defaults
28
- myapi org create --name "Acme" --yes # creates an org and sets it as default
29
- myapi org list # see all orgs and their IDs
30
- ```
31
-
32
- After `myapi auth setup` your `api_key` and default `org_id` are stored locally. Most commands pick them up automatically.
33
-
34
- ## Organizations
30
+ - **Anonymous** (`myapi auth setup --anonymous`): zero-friction account creation. **Starts with $0 credit.** Good for catalog browsing, reading help, inspecting schemas — nothing that costs upstream money. The agent-onboarding path: provisions an account in one call, no email needed.
31
+ - **Registered** (verified email via `myapi auth link <email>`): unlocks $5 free credit and the paid surface (LLM, image, email, domain register, etc.). Required for `myapi billing setup` and anything that hits Stripe.
35
32
 
36
- An org is the container for everything elsedomains, funnels, brand settings.
33
+ An anonymous account can upgrade at any time via `myapi auth link <email>` the credit grants on successful verification. Anonymous accounts that need paid actions hit a friendly `INSUFFICIENT_BALANCE` or `REGISTRATION_REQUIRED` error pointing at `myapi auth link`.
34
+ <!-- llm:end -->
37
35
 
38
- | CLI command | What it does |
36
+ ## Commands
37
+ <!-- generated:start -->
38
+ | Command | What it does |
39
39
  |---|---|
40
- | `myapi org create --name "..."` | Create a new org (add `--yes` to auto-set as default) |
41
- | `myapi org sync-brand <domain>` | Scrape a live site and auto-fill brand info into an existing org |
40
+ | `myapi auth setup` | Interactive setup: creates account, generates api_key, sets defaults |
41
+ | `myapi auth whoami` | Show current account, default org/funnel, balance, free-tier usage |
42
+ | `myapi auth link [email]` | Upgrade anonymous account to registered (or add a second session) |
43
+ | `myapi auth switch [index]` | Switch active account |
42
44
  | `myapi org list` | List all orgs (`*` marks the default) |
43
- | `myapi org get [id]` | Inspect one org (defaults to current default org) |
44
- | `myapi org delete <id>` | Delete org and cascade |
45
-
46
- `org sync-brand` is async — it scrapes the site and updates the org's name, logo, and description. You need an existing org first (`org create`).
47
-
48
- Each org gets a free preview subdomain (`*.makeautonomous.com`) you can use before registering a custom domain.
49
-
50
- ## API Keys
51
-
52
- Keys have the format `hq_live_...` and are used as `Authorization: Bearer <api_key>` across all services.
53
-
45
+ | `myapi org create --name "..."` | Create a new org (`--yes` auto-sets as default) |
46
+ | `myapi org get [id]` | Inspect one org (defaults to current default) |
47
+ | `myapi org update [id]` | Update fields (name, tagline, description, business-sector, logo-url) |
48
+ | `myapi org delete <id>` | Delete an org and cascade |
49
+ | `myapi org sync-brand <domain>` | Scrape a live site and auto-fill brand info |
50
+ | `myapi keys list / create / revoke <id>` | Manage API keys (alias `myapi auth api-keys`) |
51
+ | `myapi billing balance` | Check balance |
52
+ | `myapi billing topup <amount>` | Top up by dollar amount |
53
+ | `myapi billing history` | Recent transactions |
54
+ | `myapi config set-org <id>` / `set-funnel <id>` / `set-domain <name>` | Set CLI defaults |
55
+ | `myapi install-skills` | Install agent skill files into ~/.claude/, ~/.gemini/, ~/.cursor/ |
56
+ <!-- generated:end -->
57
+
58
+ ## Examples
59
+ <!-- llm:start -->
54
60
  ```bash
55
- myapi keys list
56
- myapi keys create
57
- myapi keys revoke <id>
58
- ```
61
+ # Cold start: provision account + default org
62
+ myapi auth setup
63
+ myapi org create "Acme" --yes
59
64
 
60
- ## Billing
65
+ # Day-to-day
66
+ myapi auth whoami # confirm what's active
67
+ myapi billing balance # before doing anything that costs credits
68
+ myapi billing topup 20 # add $20
61
69
 
62
- All services consume credits. Check balance and top up here.
70
+ # Sync brand info from an existing website
71
+ myapi org sync-brand acme.com
63
72
 
64
- ```bash
65
- myapi billing balance
66
- myapi billing topup <amount> # e.g. myapi billing topup 20
67
- myapi billing history
73
+ # Switch between multiple accounts
74
+ myapi auth switch 2
68
75
  ```
69
76
 
70
77
  If any service returns `402`, run `myapi billing balance` and top up before retrying.
71
78
 
72
- ## Config Defaults
79
+ Each org gets a free preview subdomain (`*.makeautonomous.com`) usable before registering a custom domain.
80
+ <!-- llm:end -->
73
81
 
74
- Set defaults once to skip `--org` on every command:
82
+ ## Notes
75
83
 
76
- ```bash
77
- myapi auth config set-org <org_id>
78
- myapi auth config set-domain <domain>
79
- myapi auth config show
80
- ```
84
+ - Set `--org` defaults once with `myapi config set-org <id>` to skip the flag on every command.
85
+ - API keys have format `hq_live_...` and are sent as `Authorization: Bearer <key>`.
86
+ - `org sync-brand` is async (scrapes the site, polls the job).
81
87
 
82
88
  Run `myapi --help` or `myapi <command> --help` for full flag reference.
@@ -0,0 +1,42 @@
1
+ # my-crm-api
2
+
3
+ The canonical store of **engaged** people and companies — private to your org. Pairs with **mypeopleapi** (Goldfox-sourced global lead universe, read-only) and **myaudienceapi** (saved filter snapshots): people discovered there get promoted into the CRM the first time they're engaged.
4
+
5
+ ## What it does
6
+
7
+ - **Contacts** (people) and **companies** with a fixed lifecycle-stage enum
8
+ - **Auto-ingest** from inbound webhooks via a configurable dot-path (set on the webhook endpoint)
9
+ - **Event timeline** per contact, append-only, with a closed set of reserved kinds
10
+ - **Goldfox promote** — turn a discovered lead into a CRM contact in one call (idempotent)
11
+ - **Soft delete** with restore — events are retained on deleted contacts
12
+ - **Auto-link** contacts to companies by email domain (creates the company on first sight)
13
+
14
+ ## Quickstart
15
+
16
+ ```bash
17
+ # Promote a Goldfox lead
18
+ myapi crm contacts promote p_MC0xMDUuY29t.0
19
+
20
+ # List contacts in stage 'qualified'
21
+ myapi crm contacts search --stage qualified
22
+
23
+ # Move someone forward
24
+ myapi crm contacts update <id> --stage qualified
25
+
26
+ # See what's happened to a contact
27
+ myapi crm contacts events <id>
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ ```bash
33
+ export MYAPI_KEY=mak_...
34
+ ```
35
+
36
+ Requires `api_key` and `org_id` from **myapihq**.
37
+
38
+ ## Documentation
39
+
40
+ Schema, enums, auto-ingest contract, search filter semantics: see `SKILL.md`.
41
+
42
+ Run `myapi crm --help` for inline reference.