@myapihq/cli 2.31.3 → 2.31.5
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/domain.js +3 -0
- package/dist/commands/pixel.js +9 -5
- package/dist/commands/status.js +5 -0
- package/dist/errors.js +10 -1
- package/dist/errors.test.js +26 -0
- package/dist/registrant.js +16 -1
- package/dist/registrant.test.d.ts +1 -0
- package/dist/registrant.test.js +39 -0
- package/dist/skills/my-pixel-api/SKILL.md +2 -2
- package/package.json +4 -2
package/dist/commands/domain.js
CHANGED
|
@@ -51,6 +51,9 @@ export const SCHEMA = {
|
|
|
51
51
|
'registrant-state': 'string',
|
|
52
52
|
'registrant-postal-code': 'string',
|
|
53
53
|
'registrant-country': 'string',
|
|
54
|
+
// Alias for --registrant-country, kept because account declared this
|
|
55
|
+
// spelling for the same field. See fromFlags in registrant.ts.
|
|
56
|
+
'registrant-country-code': 'string',
|
|
54
57
|
'registrant-organization': 'string',
|
|
55
58
|
// DNS records sub-surface
|
|
56
59
|
type: 'string',
|
package/dist/commands/pixel.js
CHANGED
|
@@ -26,7 +26,7 @@ export async function interactions(flags) {
|
|
|
26
26
|
const config = requireConfig();
|
|
27
27
|
const orgId = flags.org || config.default_org;
|
|
28
28
|
if (!orgId) {
|
|
29
|
-
error("Missing required arguments.\nUsage: myapi pixel interactions --
|
|
29
|
+
error("Missing required arguments.\nUsage: myapi pixel interactions (--website <host> | --campaign-id <id> | --domain <domain>) [--org <id>]\n --website: a site YOU own, where your pixel runs · --domain: a domain your visitors went to\n(Or set defaults via: myapi config set-org <id>)");
|
|
30
30
|
}
|
|
31
31
|
const params = {};
|
|
32
32
|
if (flags.website)
|
|
@@ -44,7 +44,7 @@ export async function interactions(flags) {
|
|
|
44
44
|
if (flags.offset)
|
|
45
45
|
params.offset = parseInt(flags.offset, 10);
|
|
46
46
|
if (!params.website && !params.campaign_id && !params.domain) {
|
|
47
|
-
error("You must provide at least one filter
|
|
47
|
+
error("You must provide at least one filter.\n --website <host> a site you own (where your pixel runs)\n --campaign-id <id> one email campaign\n --domain <domain> a domain your visitors went to");
|
|
48
48
|
}
|
|
49
49
|
const res = await sdkPixel.getInteractions(config.api_key, orgId, params);
|
|
50
50
|
if (flags.json) {
|
|
@@ -87,7 +87,7 @@ export async function visits(flags) {
|
|
|
87
87
|
ts: v.ts,
|
|
88
88
|
})));
|
|
89
89
|
// /visits returns only {visits, total} — it does NOT echo limit/offset the
|
|
90
|
-
// way /
|
|
90
|
+
// way /interactions does, so this used to print
|
|
91
91
|
// "Showing: undefined | Offset: undefined". Report what the response
|
|
92
92
|
// actually carries.
|
|
93
93
|
info(`Total: ${res.total} | Showing: ${res.visits.length}`);
|
|
@@ -128,7 +128,11 @@ export async function events(flags) {
|
|
|
128
128
|
campaign_id: e.campaign_id ?? '',
|
|
129
129
|
ts: e.ts,
|
|
130
130
|
})));
|
|
131
|
-
|
|
131
|
+
// /events returns {events, total} — the limit/offset echo the visits comment
|
|
132
|
+
// above attributes to it is in `meta`, which the SDK does not surface, so
|
|
133
|
+
// this printed "Showing: undefined | Offset: undefined" too. Only
|
|
134
|
+
// /interactions genuinely echoes them in the body.
|
|
135
|
+
info(`Total: ${res.total} | Showing: ${res.events.length}`);
|
|
132
136
|
}
|
|
133
137
|
// Geographic distribution sample of the org's pixel audience.
|
|
134
138
|
export async function audience(flags) {
|
|
@@ -191,7 +195,7 @@ Subcommands:
|
|
|
191
195
|
identify Link a known email/user id to an anonymous pixel visitor
|
|
192
196
|
identity Resolve the identity graph (emails, IPs, profiles) for a pixel ID
|
|
193
197
|
interactions Get a unified timeline of visits and events
|
|
194
|
-
(requires
|
|
198
|
+
(requires one of: --website = a site you own · --campaign-id · --domain = a visited domain)
|
|
195
199
|
visits Page-visit timeline scoped to a website host
|
|
196
200
|
|
|
197
201
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
package/dist/commands/status.js
CHANGED
|
@@ -113,8 +113,13 @@ export async function run(_subcommand, _args, flags = {}) {
|
|
|
113
113
|
info(`Recharge: ${summary}`);
|
|
114
114
|
}
|
|
115
115
|
if (Array.isArray(freeTier) && freeTier.length > 0) {
|
|
116
|
+
// A zero allowance rendered as "domain 0/0" reads like an error and says
|
|
117
|
+
// nothing a user can act on — there is no free tier for that slot, which
|
|
118
|
+
// is the platform's default, not this account's state. Show a slot only
|
|
119
|
+
// when there is an allowance to consume or usage to explain.
|
|
116
120
|
const parts = freeTier
|
|
117
121
|
.filter((e) => e && typeof e.service === 'string')
|
|
122
|
+
.filter((e) => (e.allowance ?? 0) > 0 || (e.used ?? 0) > 0)
|
|
118
123
|
.map((e) => `${e.service} ${e.used ?? 0}/${e.allowance ?? '?'}`);
|
|
119
124
|
if (parts.length > 0)
|
|
120
125
|
info(`FreeTier: ${parts.join(' · ')}`);
|
package/dist/errors.js
CHANGED
|
@@ -144,7 +144,16 @@ export function friendlyError(err) {
|
|
|
144
144
|
lines.push(err.requestId
|
|
145
145
|
? ` Quote this when reporting it: ${err.requestId}`
|
|
146
146
|
: ' No request id came back, so quote the exact command and the time instead.');
|
|
147
|
-
|
|
147
|
+
// Only for a request that could have changed something. A 500 can land
|
|
148
|
+
// before or after a write, so the warning is right for POST/PATCH/DELETE —
|
|
149
|
+
// but `pixel audience` is a GET, and telling someone to go and check
|
|
150
|
+
// whether their read went through sends them looking for a change that
|
|
151
|
+
// could not exist. Unknown method keeps the warning: silence about a
|
|
152
|
+
// possible half-applied write is the worse failure.
|
|
153
|
+
const read = err.method === 'GET' || err.method === 'HEAD';
|
|
154
|
+
if (!read) {
|
|
155
|
+
lines.push(' Whether the change went through is not knowable from here — check before retrying.');
|
|
156
|
+
}
|
|
148
157
|
return withOrgContext(lines.join('\n'), err);
|
|
149
158
|
}
|
|
150
159
|
const base = ERROR_MESSAGES[err.code] || err.code;
|
package/dist/errors.test.js
CHANGED
|
@@ -139,3 +139,29 @@ function looksLikeBadKey(err) {
|
|
|
139
139
|
]);
|
|
140
140
|
return err.status === 401 && (!err.code || AUTH_CODES.has(String(err.code).toLowerCase()));
|
|
141
141
|
}
|
|
142
|
+
// A 500 on a GET used to end with "Whether the change went through is not
|
|
143
|
+
// knowable from here — check before retrying." `pixel audience` is a read; it
|
|
144
|
+
// sent people to look for a change that could not exist. Found 2026-08-24 while
|
|
145
|
+
// fixing PIXEL_BASE, when the command finally reached the platform at all.
|
|
146
|
+
describe('5xx advice distinguishes a read from a write', () => {
|
|
147
|
+
function err(method) {
|
|
148
|
+
const e = new MyApiError('ANALYTICS_ERROR', 502, 'analytics query failed');
|
|
149
|
+
e.requestId = 'req_test';
|
|
150
|
+
if (method)
|
|
151
|
+
e.method = method;
|
|
152
|
+
return e;
|
|
153
|
+
}
|
|
154
|
+
it('says nothing about a change after a failed GET', () => {
|
|
155
|
+
const msg = friendlyError(err('GET'));
|
|
156
|
+
expect(msg).not.toContain('went through');
|
|
157
|
+
expect(msg).toContain('req_test');
|
|
158
|
+
expect(msg).toContain('our fault');
|
|
159
|
+
});
|
|
160
|
+
it('still warns after a failed write', () => {
|
|
161
|
+
expect(friendlyError(err('POST'))).toContain('went through');
|
|
162
|
+
expect(friendlyError(err('DELETE'))).toContain('went through');
|
|
163
|
+
});
|
|
164
|
+
it('keeps the warning when the method is unknown', () => {
|
|
165
|
+
expect(friendlyError(err())).toContain('went through');
|
|
166
|
+
});
|
|
167
|
+
});
|
package/dist/registrant.js
CHANGED
|
@@ -21,6 +21,21 @@ import { saveConfig } from './config.js';
|
|
|
21
21
|
// whether the partial is "complete enough" (we require all six base
|
|
22
22
|
// fields plus country_code; state is optional).
|
|
23
23
|
function fromFlags(flags) {
|
|
24
|
+
// Two spellings exist in the wild: account's schema declared
|
|
25
|
+
// `registrant-country-code` while this reader only ever read
|
|
26
|
+
// `registrant-country` — so the -code spelling was typed, swallowed a value,
|
|
27
|
+
// and did nothing. A scripted `account registrant set` then failed with
|
|
28
|
+
// "country_code missing" while the country sat in argv. Accept both;
|
|
29
|
+
// `registrant-country` is canonical (matching the rest of the family, which
|
|
30
|
+
// carries no type suffixes). Both-with-different-values is refused rather
|
|
31
|
+
// than resolved by precedence.
|
|
32
|
+
const country = flags['registrant-country'];
|
|
33
|
+
const countryAlias = flags['registrant-country-code'];
|
|
34
|
+
if (typeof country === 'string' && typeof countryAlias === 'string' && country !== countryAlias) {
|
|
35
|
+
error(`--registrant-country ("${country}") and --registrant-country-code ("${countryAlias}") disagree — they are the same field. Pass one.`);
|
|
36
|
+
}
|
|
37
|
+
const countryCode = typeof country === 'string' ? country
|
|
38
|
+
: typeof countryAlias === 'string' ? countryAlias : undefined;
|
|
24
39
|
return {
|
|
25
40
|
name: typeof flags['registrant-name'] === 'string' ? flags['registrant-name'] : undefined,
|
|
26
41
|
email: typeof flags['registrant-email'] === 'string' ? flags['registrant-email'] : undefined,
|
|
@@ -29,7 +44,7 @@ function fromFlags(flags) {
|
|
|
29
44
|
city: typeof flags['registrant-city'] === 'string' ? flags['registrant-city'] : undefined,
|
|
30
45
|
state: typeof flags['registrant-state'] === 'string' ? flags['registrant-state'] : undefined,
|
|
31
46
|
postal_code: typeof flags['registrant-postal-code'] === 'string' ? flags['registrant-postal-code'] : undefined,
|
|
32
|
-
country_code:
|
|
47
|
+
country_code: countryCode,
|
|
33
48
|
organization: typeof flags['registrant-organization'] === 'string' ? flags['registrant-organization'] : undefined,
|
|
34
49
|
};
|
|
35
50
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveRegistrantForRegister } from './registrant.js';
|
|
3
|
+
import { withSink, CommandFailed } from './output.js';
|
|
4
|
+
// error() exits the process unless a sink captures it — same pattern as
|
|
5
|
+
// dispatch-in-process.test.ts.
|
|
6
|
+
const SINK = { out: () => { }, err: () => { }, fail: (m) => { throw new CommandFailed(m); } };
|
|
7
|
+
// `--registrant-country-code` was declared in account's SCHEMA but read by
|
|
8
|
+
// nothing: fromFlags only ever looked at `registrant-country`. The -code
|
|
9
|
+
// spelling consumed a value and discarded it, so a scripted registrant set
|
|
10
|
+
// failed with "country_code missing" while the country sat in argv. Found
|
|
11
|
+
// 2026-08-24 by dumping every SCHEMA key and asking which have no consumer.
|
|
12
|
+
// Both spellings are read now; these tests pin that to behaviour, not to a
|
|
13
|
+
// schema entry a lint could satisfy without a consumer.
|
|
14
|
+
const BASE = {
|
|
15
|
+
'registrant-name': 'Ada Lovelace',
|
|
16
|
+
'registrant-email': 'ada@example.com',
|
|
17
|
+
'registrant-phone': '+44.2071234567',
|
|
18
|
+
'registrant-street': '12 Analytical Way',
|
|
19
|
+
'registrant-city': 'London',
|
|
20
|
+
'registrant-postal-code': 'EC1A 1AA',
|
|
21
|
+
};
|
|
22
|
+
const CONFIG = {};
|
|
23
|
+
describe('registrant country spellings', () => {
|
|
24
|
+
it('canonical --registrant-country reaches country_code', async () => {
|
|
25
|
+
const r = await resolveRegistrantForRegister({ ...BASE, 'registrant-country': 'GB' }, CONFIG);
|
|
26
|
+
expect(r.country_code).toBe('GB');
|
|
27
|
+
});
|
|
28
|
+
it('the alias --registrant-country-code reaches country_code too', async () => {
|
|
29
|
+
const r = await resolveRegistrantForRegister({ ...BASE, 'registrant-country-code': 'GB' }, CONFIG);
|
|
30
|
+
expect(r.country_code).toBe('GB');
|
|
31
|
+
});
|
|
32
|
+
it('both spellings with different values are refused, not resolved by precedence', async () => {
|
|
33
|
+
await expect(withSink(SINK, () => resolveRegistrantForRegister({ ...BASE, 'registrant-country': 'GB', 'registrant-country-code': 'DE' }, CONFIG))).rejects.toThrow(/disagree/);
|
|
34
|
+
});
|
|
35
|
+
it('both spellings agreeing is fine', async () => {
|
|
36
|
+
const r = await resolveRegistrantForRegister({ ...BASE, 'registrant-country': 'GB', 'registrant-country-code': 'GB' }, CONFIG);
|
|
37
|
+
expect(r.country_code).toBe('GB');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Tracking pixel + identity resolution for MyAPI funnels and email. Capture visits and events, resolve known users to anonymous sessions, stream interaction events for analytics. Pairs with mycrmapi for auto-ingest of pixel_visit events on known contacts.
|
|
6
6
|
triggers: [pixel, analytics, tracking, visit, event, identity, session, attribution, geo, open pixel]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-4be497d5db05dbca3646b76b3385c22f84de4726d1049a7d9876c054159e4a58
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyPixelAPI
|
|
@@ -103,7 +103,7 @@ graph across the visitor's sessions.
|
|
|
103
103
|
<!-- http:start -->
|
|
104
104
|
<!-- generated by `npm run canonical-sync` — do not edit -->
|
|
105
105
|
```
|
|
106
|
-
base https://api.
|
|
106
|
+
base https://api.myapihq.com
|
|
107
107
|
path POST /pixel/orgs/{org_id}/identify
|
|
108
108
|
auth Authorization: Bearer <key> (fn: env.__MYAPI_KEY · container: env.MYAPI_KEY)
|
|
109
109
|
reply { "success": true, "data": …, "error": null, "meta": {…} }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.31.
|
|
4
|
+
"version": "2.31.5",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"lint:help-order": "node scripts/lint-help-order.js",
|
|
36
36
|
"lint:exposes": "node scripts/lint-exposes.js",
|
|
37
37
|
"lint:query-params": "node scripts/lint-query-params.js",
|
|
38
|
+
"lint:flag-lexicon": "node scripts/lint-flag-lexicon.js",
|
|
39
|
+
"lint:verb-symmetry": "node scripts/lint-verb-symmetry.js",
|
|
38
40
|
"lint:request-fields": "node scripts/lint-request-fields.js",
|
|
39
41
|
"audit:doctor": "npm run build && node scripts/audit-doctor.js",
|
|
40
42
|
"lint:docs": "node scripts/lint-docs.js",
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
|
-
"@myapihq/sdk": "^2.31.
|
|
52
|
+
"@myapihq/sdk": "^2.31.5"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@types/node": "^25.6.0",
|