@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.
- package/dist/commands/{email.d.ts → audience.d.ts} +2 -0
- package/dist/commands/audience.js +245 -0
- package/dist/commands/auth.d.ts +5 -2
- package/dist/commands/auth.js +133 -5
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +18 -1
- package/dist/commands/company.d.ts +6 -0
- package/dist/commands/company.js +149 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +20 -0
- package/dist/commands/crm/companies.d.ts +6 -0
- package/dist/commands/crm/companies.js +193 -0
- package/dist/commands/crm/contacts.d.ts +6 -0
- package/dist/commands/crm/contacts.js +241 -0
- package/dist/commands/crm/index.d.ts +6 -0
- package/dist/commands/crm/index.js +74 -0
- package/dist/commands/database.d.ts +6 -0
- package/dist/commands/database.js +259 -0
- package/dist/commands/domain.d.ts +3 -0
- package/dist/commands/domain.js +67 -1
- package/dist/commands/email/campaign.d.ts +2 -0
- package/dist/commands/email/campaign.js +34 -1
- package/dist/commands/email/index.d.ts +2 -0
- package/dist/commands/email/index.js +9 -0
- package/dist/commands/email/mailbox.d.ts +2 -0
- package/dist/commands/email/mailbox.js +5 -0
- package/dist/commands/email/message.d.ts +2 -0
- package/dist/commands/email/message.js +8 -0
- package/dist/commands/email/template.d.ts +2 -0
- package/dist/commands/email/template.js +54 -0
- package/dist/commands/email/verify.d.ts +7 -0
- package/dist/commands/email/verify.js +52 -0
- package/dist/commands/email/warmup.d.ts +2 -0
- package/dist/commands/email/warmup.js +7 -0
- package/dist/commands/funnel.d.ts +3 -0
- package/dist/commands/funnel.js +31 -0
- package/dist/commands/image.d.ts +2 -0
- package/dist/commands/image.js +43 -9
- package/dist/commands/keys.d.ts +2 -0
- package/dist/commands/keys.js +5 -0
- package/dist/commands/llm.d.ts +6 -0
- package/dist/commands/llm.js +156 -0
- package/dist/commands/org.d.ts +3 -0
- package/dist/commands/org.js +55 -0
- package/dist/commands/people.d.ts +6 -0
- package/dist/commands/people.js +136 -0
- package/dist/commands/pixel.d.ts +11 -3
- package/dist/commands/pixel.js +93 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +57 -5
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +154 -0
- package/dist/commands/storage.d.ts +2 -0
- package/dist/commands/storage.js +14 -2
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +3 -0
- package/dist/commands/url.d.ts +7 -2
- package/dist/commands/url.js +6 -0
- package/dist/commands/verify.d.ts +7 -0
- package/dist/commands/verify.js +56 -0
- package/dist/commands/webhook.d.ts +2 -0
- package/dist/commands/webhook.js +31 -6
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +19 -1
- package/dist/completion.d.ts +3 -0
- package/dist/completion.js +78 -0
- package/dist/config.d.ts +2 -0
- package/dist/exposes.d.ts +2 -0
- package/dist/exposes.js +13 -0
- package/dist/exposes.test.js +99 -0
- package/dist/flags.test.js +84 -0
- package/dist/index.js +106 -14
- package/dist/prompt.d.ts +15 -0
- package/dist/prompt.js +27 -0
- package/dist/registrant.d.ts +6 -0
- package/dist/registrant.js +155 -0
- package/dist/services-sync.test.d.ts +1 -0
- package/dist/services-sync.test.js +32 -0
- package/dist/skills/my-api-hq/SKILL.md +51 -45
- package/dist/skills/my-crm-api/README.md +42 -0
- package/dist/skills/my-crm-api/SKILL.md +165 -0
- package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-database-api/README.md +41 -0
- package/dist/skills/my-database-api/SKILL.md +107 -0
- package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-domain-api/SKILL.md +53 -26
- package/dist/skills/my-email-verify-api/README.md +33 -0
- package/dist/skills/my-email-verify-api/SKILL.md +91 -0
- package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-funnel-api/SKILL.md +41 -37
- package/dist/skills/my-image-api/SKILL.md +48 -44
- package/dist/skills/my-llm-api/README.md +35 -0
- package/dist/skills/my-llm-api/SKILL.md +125 -0
- package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-storage-api/README.md +1 -1
- package/dist/skills/my-storage-api/SKILL.md +44 -54
- package/dist/skills/my-webhook-api/README.md +1 -1
- package/dist/skills/my-webhook-api/SKILL.md +48 -92
- package/dist/skills/my-workflow-api/SKILL.md +46 -115
- package/dist/telemetry.d.ts +12 -0
- package/dist/telemetry.js +80 -0
- package/dist/telemetry.test.d.ts +1 -0
- package/dist/telemetry.test.js +101 -0
- package/package.json +13 -8
- package/dist/commands/email.js +0 -586
- package/dist/skills/my-email-api/README.md +0 -45
- package/dist/skills/my-email-api/SKILL.md +0 -104
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
- package/dist/skills/my-email-api/make/.gitkeep +0 -0
- package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
- package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-image-api/make/.gitkeep +0 -0
- package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-storage-api/make/.gitkeep +0 -0
- package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
- package/dist/utils.test.js +0 -48
- /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
- /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
- /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
- /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
- /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
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
Two tiers, chosen at setup time:
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
myapi auth
|
|
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
|
|
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
|
-
|
|
36
|
+
## Commands
|
|
37
|
+
<!-- generated:start -->
|
|
38
|
+
| Command | What it does |
|
|
39
39
|
|---|---|
|
|
40
|
-
| `myapi
|
|
41
|
-
| `myapi
|
|
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
|
|
44
|
-
| `myapi org
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
myapi
|
|
57
|
-
myapi
|
|
58
|
-
```
|
|
61
|
+
# Cold start: provision account + default org
|
|
62
|
+
myapi auth setup
|
|
63
|
+
myapi org create "Acme" --yes
|
|
59
64
|
|
|
60
|
-
|
|
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
|
-
|
|
70
|
+
# Sync brand info from an existing website
|
|
71
|
+
myapi org sync-brand acme.com
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
myapi
|
|
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
|
-
|
|
79
|
+
Each org gets a free preview subdomain (`*.makeautonomous.com`) usable before registering a custom domain.
|
|
80
|
+
<!-- llm:end -->
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
## Notes
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|