@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
@@ -4,6 +4,17 @@ import { success, error, printTable, printJson, info } from '../output.js';
4
4
  import { confirm } from '../prompt.js';
5
5
  import { formatDate, pollJob } from '../utils.js';
6
6
  import { requireOrg, requireArg } from '../helpers.js';
7
+ export const EXPOSES = [
8
+ 'POST /hq/orgs',
9
+ 'GET /hq/orgs',
10
+ 'GET /hq/orgs/{org_id}',
11
+ 'PATCH /hq/orgs/{org_id}',
12
+ 'DELETE /hq/orgs/{org_id}',
13
+ 'POST /hq/org-imports',
14
+ 'GET /hq/org-imports/{import_id}',
15
+ 'POST /hq/org-imports/{import_id}/confirm',
16
+ 'GET /funnel/orgs/{org_id}/funnels',
17
+ ];
7
18
  export const SCHEMA = {
8
19
  name: 'string',
9
20
  tagline: 'string',
@@ -107,6 +118,38 @@ export async function del(id, _flags) {
107
118
  await hq.deleteOrg(config.api_key, id);
108
119
  success(`Org ${id} deleted`);
109
120
  }
121
+ // Update one or more fields of an org. At least one --flag must be supplied;
122
+ // otherwise we have nothing to send (the SDK accepts a partial payload but
123
+ // PATCH with an empty body would be a wasted round-trip and a confusing
124
+ // no-op for the user).
125
+ export async function update(restArgs, flags) {
126
+ const config = requireConfig();
127
+ const orgId = restArgs[0] || flags.org || config.default_org;
128
+ if (!orgId)
129
+ error('Missing org id. Usage: myapi org update <id> [--field <value> ...]\n(Or set a default: myapi config set-org <id>)');
130
+ const payload = {};
131
+ if (flags.name)
132
+ payload.name = flags.name;
133
+ if (flags.tagline)
134
+ payload.tagline = flags.tagline;
135
+ if (flags.description)
136
+ payload.description = flags.description;
137
+ if (flags['business-sector'])
138
+ payload.business_sector = flags['business-sector'];
139
+ if (flags['logo-url'])
140
+ payload.logo_url = flags['logo-url'];
141
+ if (Object.keys(payload).length === 0) {
142
+ error('No update flags provided. Pass at least one of: --name, --tagline, --description, --business-sector, --logo-url.');
143
+ }
144
+ const updated = await hq.updateOrg(config.api_key, orgId, payload);
145
+ if (flags.json) {
146
+ printJson(updated);
147
+ return;
148
+ }
149
+ success(`Org ${updated.id} updated`);
150
+ const fields = Object.keys(payload).map(k => `${k} = ${payload[k]}`);
151
+ info(` ${fields.join('\n ')}`);
152
+ }
110
153
  export async function importOrg(args, flags) {
111
154
  const config = requireConfig();
112
155
  const domain = args[0] || config.default_domain;
@@ -131,6 +174,16 @@ export async function importOrg(args, flags) {
131
174
  // ── Dispatcher ───────────────────────────────────────────────────────────────
132
175
  const SUBCOMMAND_USAGE = {
133
176
  'list': 'myapi org list [--json]',
177
+ 'update': `myapi org update [<id>] [--name <str>] [--tagline <str>] [--description <str>] [--business-sector <str>] [--logo-url <url>] [--json]
178
+
179
+ Updates one or more fields of an organization. Specify the org by positional
180
+ argument, --org <id>, or fall back to the default org from your config.
181
+ At least one update flag must be provided.
182
+
183
+ Examples:
184
+ myapi org update --tagline "Now with even more cowbell"
185
+ myapi org update abc-123 --name "Acme Inc" --description "Updated tagline"
186
+ myapi org update --logo-url https://example.com/logo.png --json`,
134
187
  'create': `myapi org create <name> [--tagline <str>] [--description <str>] [--business-sector <str>] [--logo-url <url>] [--yes] [--json]
135
188
  myapi org create --name <name> [...]
136
189
 
@@ -166,6 +219,7 @@ export async function run(subcommand, args, flags) {
166
219
  Subcommands:
167
220
  list List organizations
168
221
  create Create an organization (e.g. myapi org create "Name" --yes)
222
+ update Update an organization's fields (name, tagline, description, etc.)
169
223
  get Get details of an organization
170
224
  delete Delete an organization
171
225
  sync-brand Sync brand info (name, logo, description) from an existing website`);
@@ -183,6 +237,7 @@ Subcommands:
183
237
  switch (subcommand) {
184
238
  case 'list': return list(flags);
185
239
  case 'create': return create(args, flags);
240
+ case 'update': return update(args, flags);
186
241
  case 'get': return get(args[0], flags);
187
242
  case 'delete': return del(args[0], flags);
188
243
  case 'sync-brand': return importOrg(args, flags);
@@ -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>;
@@ -0,0 +1,136 @@
1
+ import { people as sdkPeople } from '@myapihq/sdk';
2
+ import { requireConfig } from '../config.js';
3
+ import { error, printTable, info, printJson } from '../output.js';
4
+ import { requireOrg, requireArg } from '../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /people/orgs/{org_id}/search',
7
+ 'GET /people/orgs/{org_id}/{person_id}',
8
+ ];
9
+ export const SCHEMA = {
10
+ org: 'string',
11
+ // Goldfox filter dimensions
12
+ confidence: 'string',
13
+ country: 'string',
14
+ 'country-consistent': 'boolean',
15
+ 'email-type': 'string',
16
+ 'has-c-level': 'boolean',
17
+ 'has-careers-page': 'boolean',
18
+ 'has-decision-maker': 'boolean',
19
+ 'has-investors-page': 'boolean',
20
+ 'has-shop-page': 'boolean',
21
+ 'is-registered-entity': 'boolean',
22
+ keyword: 'string',
23
+ 'min-headcount': 'number',
24
+ 'min-link-confidence': 'number',
25
+ 'min-source-count': 'number',
26
+ seniority: 'string',
27
+ 'tld-class': 'string',
28
+ limit: 'number',
29
+ offset: 'number',
30
+ };
31
+ // `--industry saas,fintech` → ['saas', 'fintech']. Returns undefined for
32
+ // empty/missing so we don't send empty arrays to the backend.
33
+ function csv(v) {
34
+ if (typeof v !== 'string' || v.trim() === '')
35
+ return undefined;
36
+ return v.split(',').map(s => s.trim()).filter(Boolean);
37
+ }
38
+ function buildFilter(flags) {
39
+ return {
40
+ confidence: csv(flags.confidence),
41
+ country: csv(flags.country),
42
+ country_consistent: typeof flags['country-consistent'] === 'boolean' ? flags['country-consistent'] : undefined,
43
+ email_type: csv(flags['email-type']),
44
+ has_c_level: typeof flags['has-c-level'] === 'boolean' ? flags['has-c-level'] : undefined,
45
+ has_careers_page: typeof flags['has-careers-page'] === 'boolean' ? flags['has-careers-page'] : undefined,
46
+ has_decision_maker: typeof flags['has-decision-maker'] === 'boolean' ? flags['has-decision-maker'] : undefined,
47
+ has_investors_page: typeof flags['has-investors-page'] === 'boolean' ? flags['has-investors-page'] : undefined,
48
+ has_shop_page: typeof flags['has-shop-page'] === 'boolean' ? flags['has-shop-page'] : undefined,
49
+ is_registered_entity: typeof flags['is-registered-entity'] === 'boolean' ? flags['is-registered-entity'] : undefined,
50
+ keyword: typeof flags.keyword === 'string' ? flags.keyword : undefined,
51
+ min_headcount: typeof flags['min-headcount'] === 'number' ? flags['min-headcount'] : undefined,
52
+ min_link_confidence: typeof flags['min-link-confidence'] === 'number' ? flags['min-link-confidence'] : undefined,
53
+ min_source_count: typeof flags['min-source-count'] === 'number' ? flags['min-source-count'] : undefined,
54
+ seniority: csv(flags.seniority),
55
+ tld_class: csv(flags['tld-class']),
56
+ limit: typeof flags.limit === 'number' ? flags.limit : undefined,
57
+ offset: typeof flags.offset === 'number' ? flags.offset : undefined,
58
+ };
59
+ }
60
+ async function search(flags) {
61
+ const config = requireConfig();
62
+ const orgId = requireOrg(flags, config, 'myapi people search [filters...] [--org <id>]');
63
+ const filter = buildFilter(flags);
64
+ const res = await sdkPeople.searchPeople(config.api_key, orgId, filter);
65
+ if (flags.json) {
66
+ printJson(res);
67
+ return;
68
+ }
69
+ info(`${res.people.length} of ${res.total} people${res.has_more ? ' (more available — pass --offset)' : ''}`);
70
+ printTable(res.people.map(p => ({
71
+ id: p.id,
72
+ name: p.full_name,
73
+ email: p.email ?? '',
74
+ domain: p.company?.domain ?? '',
75
+ country: p.location?.country ?? '',
76
+ link_conf: typeof p.link_confidence === 'number' ? p.link_confidence.toFixed(2) : '',
77
+ })), { flags, empty: 'No people matched. Try fewer filters.' });
78
+ }
79
+ async function get(personId, flags) {
80
+ const config = requireConfig();
81
+ const orgId = requireOrg(flags, config, 'myapi people get <person_id> [--org <id>]');
82
+ requireArg(personId, 'person_id', 'myapi people get <person_id> [--org <id>]');
83
+ const res = await sdkPeople.getPerson(config.api_key, orgId, personId);
84
+ printJson(res);
85
+ }
86
+ const SUBCOMMAND_USAGE = {
87
+ 'search': `myapi people search [--confidence <csv>] [--country <csv>] [--country-consistent]
88
+ [--email-type <csv>] [--seniority <csv>] [--tld-class <csv>]
89
+ [--has-c-level] [--has-careers-page] [--has-decision-maker]
90
+ [--has-investors-page] [--has-shop-page] [--is-registered-entity]
91
+ [--keyword <text>] [--min-headcount N] [--min-link-confidence 0..1]
92
+ [--min-source-count N] [--limit N] [--offset N] [--org <id>]
93
+
94
+ Goldfox-sourced filters; OR within an array, AND across fields.
95
+ Empty filter returns all rows with default confidence=high.
96
+
97
+ Values:
98
+ --confidence high (default), low, very_low
99
+ --seniority c_level, vp_director, manager, senior_ic, ic
100
+ --email-type corporate, freemail, role_based, other_corporate
101
+ --tld-class cctld, generic, vanity, low_trust, other
102
+ --country ISO 3166-1 alpha-2 (US, DE, GB, ...)
103
+ --keyword substring match on domain only (not name/title)
104
+
105
+ Examples:
106
+ myapi people search --seniority c_level,vp_director --country US
107
+ myapi people search --email-type corporate --has-decision-maker --min-source-count 3
108
+ myapi people search --confidence high,low --country DE --tld-class cctld`,
109
+ 'get': 'myapi people get <person_id> [--org <id>]',
110
+ };
111
+ export async function run(subcommand, args, flags) {
112
+ if (!subcommand || (flags.help && !subcommand)) {
113
+ info(`Usage: myapi people <subcommand>
114
+
115
+ Subcommands:
116
+ search Filter people across the Goldfox database
117
+ get Get a single person (with embedded company)
118
+
119
+ All commands accept --org <id> (or set a default: myapi config set-org <id>).
120
+ Filter shape is shared with company + audience — see myapi audience --help.`);
121
+ return;
122
+ }
123
+ if (flags.help) {
124
+ const usage = SUBCOMMAND_USAGE[subcommand];
125
+ if (usage)
126
+ info(`Usage: ${usage}`);
127
+ else
128
+ info(`Unknown subcommand: ${subcommand}. Run "myapi people --help" for the list.`);
129
+ return;
130
+ }
131
+ switch (subcommand) {
132
+ case 'search': return search(flags);
133
+ case 'get': return get(args[0], flags);
134
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi people --help" for valid subcommands.`);
135
+ }
136
+ }
@@ -1,3 +1,11 @@
1
- export declare function interactions(flags: Record<string, string | boolean>): Promise<void>;
2
- export declare function identity(pixelId: string, flags: Record<string, string | boolean>): Promise<void>;
3
- export declare function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>): Promise<void>;
1
+ import type { Exposes } from '../exposes.js';
2
+ import type { Flags } from '../helpers.js';
3
+ import type { FlagSchema } from '../flags.js';
4
+ export declare const SCHEMA: FlagSchema;
5
+ export declare const EXPOSES: Exposes;
6
+ export declare function interactions(flags: Flags): Promise<void>;
7
+ export declare function visits(flags: Flags): Promise<void>;
8
+ export declare function events(flags: Flags): Promise<void>;
9
+ export declare function audience(flags: Flags): Promise<void>;
10
+ export declare function identity(pixelId: string, flags: Flags): Promise<void>;
11
+ export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -1,6 +1,23 @@
1
1
  import { pixel as sdkPixel } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { error, printTable, info, printJson } from '../output.js';
4
+ export const SCHEMA = {
5
+ org: 'string',
6
+ website: 'string',
7
+ 'campaign-id': 'string',
8
+ domain: 'string',
9
+ from: 'string',
10
+ to: 'string',
11
+ limit: 'number',
12
+ offset: 'number',
13
+ };
14
+ export const EXPOSES = [
15
+ 'GET /pixel/orgs/{org_id}/interactions',
16
+ 'GET /pixel/orgs/{org_id}/visits',
17
+ 'GET /pixel/orgs/{org_id}/events',
18
+ 'GET /pixel/orgs/{org_id}/identity/{pixel_id}',
19
+ 'GET /pixel/orgs/{org_id}/audience/get_geo_sample',
20
+ ];
4
21
  export async function interactions(flags) {
5
22
  const config = requireConfig();
6
23
  const orgId = flags.org || config.default_org;
@@ -34,6 +51,73 @@ export async function interactions(flags) {
34
51
  info(`Total Visits: ${res.total_visits} | Total Events: ${res.total_events} | Showing: ${res.limit} | Offset: ${res.offset}`);
35
52
  }
36
53
  }
54
+ // Page-visit timeline scoped to a website (host).
55
+ export async function visits(flags) {
56
+ const config = requireConfig();
57
+ const orgId = flags.org || config.default_org;
58
+ if (!orgId)
59
+ error("Missing org id. Usage: myapi pixel visits --website <domain> [--org <id>]\n(Or set defaults via: myapi config set-org <id>)");
60
+ if (!flags.website)
61
+ error("Missing required arg: --website <domain>. Visits are scoped to a host.");
62
+ const params = {
63
+ website: flags.website,
64
+ };
65
+ if (flags.from)
66
+ params.from = flags.from;
67
+ if (flags.to)
68
+ params.to = flags.to;
69
+ if (flags.limit)
70
+ params.limit = parseInt(flags.limit, 10);
71
+ if (flags.offset)
72
+ params.offset = parseInt(flags.offset, 10);
73
+ const res = await sdkPixel.getVisits(config.api_key, orgId, params);
74
+ if (flags.json) {
75
+ printJson(res);
76
+ return;
77
+ }
78
+ printTable(res.visits);
79
+ info(`Total: ${res.total} | Showing: ${res.limit} | Offset: ${res.offset}`);
80
+ }
81
+ // Engagement events (open / click / page_visit / sent) — filterable by
82
+ // campaign or by domain.
83
+ export async function events(flags) {
84
+ const config = requireConfig();
85
+ const orgId = flags.org || config.default_org;
86
+ if (!orgId)
87
+ error("Missing org id. Usage: myapi pixel events [--campaign-id <id> | --domain <domain>] [--org <id>]\n(Or set defaults via: myapi config set-org <id>)");
88
+ const params = {};
89
+ if (flags['campaign-id'])
90
+ params.campaign_id = flags['campaign-id'];
91
+ if (flags.domain)
92
+ params.domain = flags.domain;
93
+ if (flags.from)
94
+ params.from = flags.from;
95
+ if (flags.to)
96
+ params.to = flags.to;
97
+ if (flags.limit)
98
+ params.limit = parseInt(flags.limit, 10);
99
+ if (flags.offset)
100
+ params.offset = parseInt(flags.offset, 10);
101
+ if (!params.campaign_id && !params.domain) {
102
+ error("You must provide at least one filter: --campaign-id or --domain.");
103
+ }
104
+ const res = await sdkPixel.getEvents(config.api_key, orgId, params);
105
+ if (flags.json) {
106
+ printJson(res);
107
+ return;
108
+ }
109
+ printTable(res.events);
110
+ info(`Total: ${res.total} | Showing: ${res.limit} | Offset: ${res.offset}`);
111
+ }
112
+ // Geographic distribution sample of the org's pixel audience.
113
+ export async function audience(flags) {
114
+ const config = requireConfig();
115
+ const orgId = flags.org || config.default_org;
116
+ if (!orgId)
117
+ error("Missing org id. Usage: myapi pixel audience [--org <id>]\n(Or set defaults via: myapi config set-org <id>)");
118
+ const res = await sdkPixel.getGeoSample(config.api_key, orgId);
119
+ printJson(res);
120
+ }
37
121
  export async function identity(pixelId, flags) {
38
122
  const config = requireConfig();
39
123
  const orgId = flags.org || config.default_org;
@@ -46,6 +130,9 @@ export async function identity(pixelId, flags) {
46
130
  // ── Dispatcher ───────────────────────────────────────────────────────────────
47
131
  const SUBCOMMAND_USAGE = {
48
132
  'interactions': 'myapi pixel interactions [--website <domain>] [--campaign-id <id>] [--domain <domain>] [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--org <id>] [--json]',
133
+ 'visits': 'myapi pixel visits --website <domain> [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--org <id>] [--json]',
134
+ 'events': 'myapi pixel events [--campaign-id <id>] [--domain <domain>] [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--org <id>] [--json]',
135
+ 'audience': 'myapi pixel audience [--org <id>]',
49
136
  'identity': 'myapi pixel identity <pixel_id> [--org <id>]',
50
137
  };
51
138
  export async function run(subcommand, args, flags) {
@@ -55,6 +142,9 @@ export async function run(subcommand, args, flags) {
55
142
  Subcommands:
56
143
  interactions Get a unified timeline of visits and events
57
144
  (requires at least one filter: --website, --campaign-id, or --domain)
145
+ visits Page-visit timeline scoped to a website host
146
+ events Engagement events (sent / open / click / page_visit) by campaign or domain
147
+ audience Geographic distribution sample of your pixel audience
58
148
  identity Resolve the identity graph (emails, IPs, profiles) for a pixel ID
59
149
 
60
150
  All commands accept --org <id> (or set default: myapi config set-org <id>).`);
@@ -70,6 +160,9 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
70
160
  }
71
161
  switch (subcommand) {
72
162
  case 'interactions': return interactions(flags);
163
+ case 'visits': return visits(flags);
164
+ case 'events': return events(flags);
165
+ case 'audience': return audience(flags);
73
166
  case 'identity': return identity(args[0], flags);
74
167
  default: error(`Unknown subcommand: ${subcommand}. Run "myapi pixel --help" for a list of valid subcommands.`);
75
168
  }
@@ -1,4 +1,6 @@
1
1
  import type { Flags } from '../helpers.js';
2
+ import type { Exposes } from '../exposes.js';
3
+ export declare const EXPOSES: Exposes;
2
4
  export declare function installSkills(): Promise<void>;
3
5
  export declare function importKey(apiKey: string, flags: Flags): Promise<void>;
4
6
  export declare function setup(flags?: Flags): Promise<void>;
@@ -5,6 +5,35 @@ import { loadConfig, saveConfig, addAccount, loadFullConfig } from '../config.js
5
5
  import { info, success } from '../output.js';
6
6
  import { ask, confirm } from '../prompt.js';
7
7
  import { hq, funnel as sdkFunnel } from '@myapihq/sdk';
8
+ import { emitSkillsInstall } from '../telemetry.js';
9
+ // Resolved at first call from package.json; used by the telemetry payload.
10
+ // Inlined as a getter so import order doesn't matter (package.json is read
11
+ // in index.ts at startup, but installSkills can be called from outside that
12
+ // path).
13
+ let _cliVersion = null;
14
+ function getCliVersion() {
15
+ if (_cliVersion !== null)
16
+ return _cliVersion;
17
+ try {
18
+ const pkgUrl = new URL('../../package.json', import.meta.url);
19
+ _cliVersion = JSON.parse(fs.readFileSync(pkgUrl, 'utf-8')).version || 'unknown';
20
+ }
21
+ catch {
22
+ _cliVersion = 'unknown';
23
+ }
24
+ return _cliVersion;
25
+ }
26
+ export const EXPOSES = [
27
+ 'POST /hq/account/anonymous',
28
+ 'POST /hq/account/send-code',
29
+ 'POST /hq/account/verify-code',
30
+ 'GET /hq/account/me',
31
+ 'GET /hq/orgs',
32
+ 'GET /funnel/orgs/{org_id}/funnels',
33
+ // installSkills() emits the skills.install telemetry event via
34
+ // ../telemetry.ts; that's an outbound POST to this endpoint.
35
+ 'POST /v1/integrations/skills-install',
36
+ ];
8
37
  // ---------------------------------------------------------------------------
9
38
  // Skills installation
10
39
  // ---------------------------------------------------------------------------
@@ -38,24 +67,40 @@ export async function installSkills() {
38
67
  // Symlink each skill into agent config directories. Surface failures so
39
68
  // users can fix permission issues — skills that didn't install will not
40
69
  // be available to the agent and "skills installed" would otherwise lie.
70
+ const agentsTargeted = [];
41
71
  for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
42
72
  try {
43
73
  fs.mkdirSync(dir, { recursive: true });
44
74
  for (const skill of skills) {
45
75
  const link = path.join(dir, skill);
46
76
  const target = path.join(SKILLS_CANONICAL, skill);
47
- // Only remove if it's already a symlink we own. Avoids clobbering a
48
- // user-authored skill that happens to share the same `my-X-api/`
49
- // name. ENOENT here just means "nothing to clean up".
77
+ // Clean up an existing entry if it's unambiguously ours, so re-runs
78
+ // are idempotent. Three cases:
79
+ // - symlink: always safe to replace (we made it).
80
+ // - directory containing only SKILL.md: legacy pre-symlink install
81
+ // style; safe to replace.
82
+ // - anything else (regular file, dir with extra content, etc.):
83
+ // leave it alone so a user-authored skill that happens to share
84
+ // a `my-X-api/` name doesn't get clobbered. The symlinkSync
85
+ // below will then fail with EEXIST and surface to the user.
86
+ // ENOENT from lstat just means "nothing there".
50
87
  try {
51
88
  const stat = fs.lstatSync(link);
52
- if (stat.isSymbolicLink())
89
+ if (stat.isSymbolicLink()) {
53
90
  fs.rmSync(link, { force: true });
91
+ }
92
+ else if (stat.isDirectory()) {
93
+ const entries = fs.readdirSync(link);
94
+ if (entries.length === 0 || (entries.length === 1 && entries[0] === 'SKILL.md')) {
95
+ fs.rmSync(link, { recursive: true, force: true });
96
+ }
97
+ }
54
98
  }
55
99
  catch { /* ENOENT: nothing there */ }
56
100
  fs.symlinkSync(target, link);
57
101
  }
58
102
  info(` ✓ ${agent}`);
103
+ agentsTargeted.push(agent);
59
104
  }
60
105
  catch (err) {
61
106
  // Skip agents not installed on this machine (ENOENT on parent dir is OK).
@@ -68,6 +113,12 @@ export async function installSkills() {
68
113
  info(` ✗ ${agent}: ${err.message || code} (${dir})`);
69
114
  }
70
115
  }
116
+ // Emit telemetry — fire-and-forget, never blocks. See packages/cli/src/telemetry.ts.
117
+ // Skipped silently if user isn't authenticated (e.g. install-skills run before
118
+ // auth setup). When at least one agent dir was successfully written.
119
+ if (agentsTargeted.length > 0) {
120
+ emitSkillsInstall(agentsTargeted, getCliVersion());
121
+ }
71
122
  }
72
123
  // ---------------------------------------------------------------------------
73
124
  // Shared helpers
@@ -194,7 +245,7 @@ export async function setup(flags = {}) {
194
245
  const activeType = existing.is_anonymous ? 'anonymous' : 'registered';
195
246
  const othersNote = total > 1 ? ` · ${total - 1} more saved` : '';
196
247
  info(`› Connected: ${activeLabel} (${activeType})${othersNote}`);
197
- const add = await confirm('› Connect a new account? (y/N) ', false);
248
+ const add = await confirm('› Connect a new account? (Y/n) ', true);
198
249
  if (!add) {
199
250
  if (!existing.skills_installed) {
200
251
  const wants = await resolveSkillsPreference(flags);
@@ -227,6 +278,7 @@ export async function setup(flags = {}) {
227
278
  }
228
279
  else {
229
280
  info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
281
+ info(' (Anonymous accounts have $0 credit. Link an email anytime to unlock $5 free credit and paid actions: myapi auth link <email>)');
230
282
  const data = await anonymousFlow();
231
283
  apiKey = data.api_key;
232
284
  accountId = data.account_id;
@@ -0,0 +1,7 @@
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 const STATUS_HELP = "Usage: myapi status [--org <id>] [--json]\n\nSingle-screen view of your account and what's running in the default org:\nidentity, balance, free-tier usage, plus resource counts across domains,\nfunnels, campaigns, webhooks, and workflows. Useful before a\nco-build session or when checking \"what's mid-flight\" without running six\nlist commands.\n\nEach section is best-effort \u2014 a failing service is shown as \"\u2014\", not an\nerror, so the rest of the snapshot still prints.\n";
7
+ export declare function run(_subcommand: string | undefined, _args: string[], flags?: Flags): Promise<void>;
@@ -0,0 +1,154 @@
1
+ import { hq, domain, funnel, email, webhook, workflow, MyApiError } from '@myapihq/sdk';
2
+ // listMailboxes is omitted from this view: the backend requires a domain or
3
+ // filter, so there is no global "all mailboxes in this org" call. Use
4
+ // `myapi email mailbox list --domain <d>` for per-domain detail.
5
+ import { loadConfig } from '../config.js';
6
+ import { info, error, printJson } from '../output.js';
7
+ export const EXPOSES = [
8
+ 'GET /hq/billing/balance',
9
+ 'GET /hq/account/free-tier',
10
+ 'GET /domain/orgs/{org_id}/domains',
11
+ 'GET /funnel/orgs/{org_id}/funnels',
12
+ 'GET /email/orgs/{org_id}/campaigns',
13
+ 'GET /webhook/orgs/{org_id}/endpoints',
14
+ 'GET /workflow/orgs/{org_id}/workflows',
15
+ ];
16
+ export const SCHEMA = {
17
+ org: 'string',
18
+ };
19
+ export const STATUS_HELP = `Usage: myapi status [--org <id>] [--json]
20
+
21
+ Single-screen view of your account and what's running in the default org:
22
+ identity, balance, free-tier usage, plus resource counts across domains,
23
+ funnels, campaigns, webhooks, and workflows. Useful before a
24
+ co-build session or when checking "what's mid-flight" without running six
25
+ list commands.
26
+
27
+ Each section is best-effort — a failing service is shown as "—", not an
28
+ error, so the rest of the snapshot still prints.
29
+ `;
30
+ function unwrap(r) {
31
+ return r.status === 'fulfilled' ? r.value : null;
32
+ }
33
+ export async function run(_subcommand, _args, flags = {}) {
34
+ if (flags.help) {
35
+ info(STATUS_HELP);
36
+ return;
37
+ }
38
+ const config = loadConfig();
39
+ if (!config?.api_key)
40
+ error('Not configured. Run: myapi auth setup');
41
+ const orgId = flags.org ?? config.default_org;
42
+ // Account-level info works without an org; org-scoped resources are skipped if none set.
43
+ const orgKnown = Boolean(orgId);
44
+ // Account-level (always fetched).
45
+ const accountResults = await Promise.allSettled([
46
+ hq.getBalance(config.api_key),
47
+ hq.getFreeTier(config.api_key),
48
+ ]);
49
+ const balance = unwrap(accountResults[0]);
50
+ // The backend currently returns an array of { service, used, allowance }
51
+ // per-service entries. The SDK type lies about this (claims scalar) — same
52
+ // bug surfaces in `whoami`. Treat as unknown here and narrow at render time.
53
+ const freeTier = unwrap(accountResults[1]);
54
+ // Catch a server-side rejection across any best-effort probe so we can flag
55
+ // the snapshot as untrustworthy rather than silently printing local config.
56
+ const keyRejected = accountResults.some(r => r.status === 'rejected' && r.reason instanceof MyApiError && r.reason.status === 401);
57
+ // Org-scoped (skipped if no default org set).
58
+ let domains = null, funnels = null, campaigns = null, webhooks = null, workflows = null;
59
+ if (orgKnown) {
60
+ const orgResults = await Promise.allSettled([
61
+ domain.listDomains(config.api_key, orgId),
62
+ funnel.listFunnels(config.api_key, orgId),
63
+ email.listCampaigns(config.api_key, orgId),
64
+ webhook.listEndpoints(config.api_key, orgId),
65
+ workflow.listWorkflows(config.api_key, orgId),
66
+ ]);
67
+ domains = unwrap(orgResults[0]);
68
+ funnels = unwrap(orgResults[1]);
69
+ campaigns = unwrap(orgResults[2]);
70
+ webhooks = unwrap(orgResults[3]);
71
+ workflows = unwrap(orgResults[4]);
72
+ }
73
+ if (flags.json) {
74
+ printJson({
75
+ account: {
76
+ email: config.email ?? null,
77
+ account_id: config.account_id,
78
+ type: config.is_anonymous ? 'anonymous' : 'registered',
79
+ default_org: orgId ?? null,
80
+ default_funnel: config.default_funnel ?? null,
81
+ key_rejected: keyRejected,
82
+ },
83
+ balance,
84
+ free_tier: freeTier,
85
+ org_resources: orgKnown ? {
86
+ domains, funnels, campaigns, webhooks, workflows,
87
+ } : null,
88
+ });
89
+ return;
90
+ }
91
+ if (keyRejected) {
92
+ info('⚠ The backend rejected your API key. Values may be stale — run: myapi auth setup');
93
+ info('');
94
+ }
95
+ // ── Identity ──────────────────────────────────────────────────────────────
96
+ if (config.email)
97
+ info(`Email: ${config.email}`);
98
+ info(`Account: ${config.account_id}`);
99
+ info(`Org: ${orgId ?? '(none — run: myapi config set-org <id>)'}`);
100
+ info(`Funnel: ${config.default_funnel ?? '(none)'}`);
101
+ info(`Type: ${config.is_anonymous ? 'anonymous' : 'registered'}`);
102
+ info(`Balance: ${balance ? `${balance.balance_display} | Credits: ${balance.credits_display}` : '—'}`);
103
+ if (config.is_anonymous) {
104
+ info(' → Link an email to unlock $5 free credit + paid actions: myapi auth link <email>');
105
+ }
106
+ if (Array.isArray(freeTier) && freeTier.length > 0) {
107
+ const parts = freeTier
108
+ .filter((e) => e && typeof e.service === 'string')
109
+ .map((e) => `${e.service} ${e.used ?? 0}/${e.allowance ?? '?'}`);
110
+ if (parts.length > 0)
111
+ info(`FreeTier: ${parts.join(' · ')}`);
112
+ }
113
+ // ── Resources ─────────────────────────────────────────────────────────────
114
+ info('');
115
+ info('Resources:');
116
+ if (!orgKnown) {
117
+ info(' (set a default org to see domains/funnels/campaigns/webhooks/workflows)');
118
+ return;
119
+ }
120
+ if (domains) {
121
+ const verified = domains.filter(d => /verified|active|ready/i.test(d.status)).length;
122
+ const pending = domains.length - verified;
123
+ const pendingStr = pending > 0 ? `, ${pending} pending` : '';
124
+ info(` Domains: ${domains.length} (${verified} verified${pendingStr})`);
125
+ }
126
+ else
127
+ info(' Domains: —');
128
+ info(` Funnels: ${funnels ? funnels.length : '—'}`);
129
+ if (campaigns) {
130
+ // Campaigns: backend statuses include 'draft', 'active', 'paused', 'completed'.
131
+ // Surface counts for the two states that matter mid-flight.
132
+ const active = campaigns.filter(c => /active|running/i.test(c.status)).length;
133
+ const paused = campaigns.filter(c => /paused/i.test(c.status)).length;
134
+ const extras = [
135
+ active > 0 ? `${active} active` : null,
136
+ paused > 0 ? `${paused} paused` : null,
137
+ ].filter(Boolean).join(', ');
138
+ info(` Campaigns: ${campaigns.length}${extras ? ` (${extras})` : ''}`);
139
+ }
140
+ else
141
+ info(' Campaigns: —');
142
+ info(` Webhooks: ${webhooks ? webhooks.length : '—'}`);
143
+ if (workflows) {
144
+ const enabled = workflows.filter(w => w.enabled).length;
145
+ const disabled = workflows.length - enabled;
146
+ const extras = [
147
+ enabled > 0 ? `${enabled} enabled` : null,
148
+ disabled > 0 ? `${disabled} disabled` : null,
149
+ ].filter(Boolean).join(', ');
150
+ info(` Workflows: ${workflows.length}${extras ? ` (${extras})` : ''}`);
151
+ }
152
+ else
153
+ info(' Workflows: —');
154
+ }
@@ -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>;