@myapihq/cli 1.3.6 → 1.3.11
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/account.d.ts +6 -0
- package/dist/commands/account.js +75 -0
- package/dist/commands/config.js +2 -2
- package/dist/commands/crm/companies.js +1 -0
- package/dist/commands/crm/contacts.js +1 -0
- package/dist/commands/doctor-setup.test.d.ts +1 -0
- package/dist/commands/doctor-setup.test.js +74 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +73 -1
- package/dist/commands/domain.d.ts +1 -0
- package/dist/commands/domain.js +38 -1
- package/dist/commands/email/mailbox.js +19 -0
- package/dist/commands/funnel.d.ts +1 -0
- package/dist/commands/funnel.js +219 -16
- package/dist/commands/llm.js +257 -24
- package/dist/commands/queue.js +1 -0
- package/dist/commands/workflow-validation.test.js +11 -2
- package/dist/commands/workflow.js +15 -2
- package/dist/completion.js +4 -3
- package/dist/exposes.test.js +1 -0
- package/dist/index.js +7 -0
- package/dist/skills/my-funnel-api/SKILL.md +35 -5
- package/dist/skills/my-llm-api/README.md +18 -8
- package/dist/skills/my-llm-api/SKILL.md +91 -62
- package/dist/skills/my-webhook-api/SKILL.md +4 -2
- package/package.json +5 -4
|
@@ -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 SCHEMA: FlagSchema;
|
|
5
|
+
export declare const EXPOSES: Exposes;
|
|
6
|
+
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// `myapi account` — account-scoped operations.
|
|
2
|
+
//
|
|
3
|
+
// Today: mailing-address (CAN-SPAM compliance for transactional email).
|
|
4
|
+
// Future home for any other account-level reads/writes that don't fit
|
|
5
|
+
// `billing` (which owns money) or `keys` (which owns auth credentials).
|
|
6
|
+
import { hq as sdkHq } from '@myapihq/sdk';
|
|
7
|
+
import { requireConfig } from '../config.js';
|
|
8
|
+
import { success, error, info, printJson } from '../output.js';
|
|
9
|
+
export const SCHEMA = {};
|
|
10
|
+
export const EXPOSES = [
|
|
11
|
+
'GET /hq/account/mailing-address',
|
|
12
|
+
'PATCH /hq/account/mailing-address',
|
|
13
|
+
];
|
|
14
|
+
const SUBCOMMAND_USAGE = {
|
|
15
|
+
'mailing-address': `myapi account mailing-address Show current value (null if unset)
|
|
16
|
+
myapi account mailing-address "<address>" Set the mailing address
|
|
17
|
+
|
|
18
|
+
The mailing address is required for transactional email (CAN-SPAM):
|
|
19
|
+
\`email message send\` returns MAILING_ADDRESS_REQUIRED until this is set.
|
|
20
|
+
Pass the full postal address as a single string — e.g.
|
|
21
|
+
"123 Main St, Springfield, IL 62701, USA".`,
|
|
22
|
+
};
|
|
23
|
+
async function mailingAddress(args, flags) {
|
|
24
|
+
const config = requireConfig();
|
|
25
|
+
// Positional arg present → set. Absent → get.
|
|
26
|
+
const value = args[0];
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
const res = await sdkHq.getMailingAddress(config.api_key);
|
|
29
|
+
if (flags.json) {
|
|
30
|
+
printJson(res);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (res.mailing_address) {
|
|
34
|
+
info(`Mailing address: ${res.mailing_address}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
info('Mailing address: (not set)');
|
|
38
|
+
info('Set it with: myapi account mailing-address "<address>"');
|
|
39
|
+
info('Required for: myapi email message send (CAN-SPAM).');
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Reject obviously bad inputs early — the backend would 400 anyway.
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
if (!trimmed) {
|
|
46
|
+
error('Mailing address must be a non-empty string. Example: "123 Main St, Springfield, IL 62701, USA".');
|
|
47
|
+
}
|
|
48
|
+
const res = await sdkHq.setMailingAddress(config.api_key, trimmed);
|
|
49
|
+
if (flags.json) {
|
|
50
|
+
printJson(res);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
success(`Mailing address set: ${res.mailing_address}`);
|
|
54
|
+
}
|
|
55
|
+
export async function run(subcommand, args, flags) {
|
|
56
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
57
|
+
info(`Usage: myapi account <subcommand>
|
|
58
|
+
|
|
59
|
+
Subcommands:
|
|
60
|
+
mailing-address Get or set the account's mailing address (CAN-SPAM)`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (flags.help) {
|
|
64
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
65
|
+
if (usage)
|
|
66
|
+
info(`Usage: ${usage}`);
|
|
67
|
+
else
|
|
68
|
+
info(`Unknown subcommand: ${subcommand}. Run "myapi account --help" for the list.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
switch (subcommand) {
|
|
72
|
+
case 'mailing-address': return mailingAddress(args, flags);
|
|
73
|
+
default: error(`Unknown subcommand: ${subcommand}. Run "myapi account --help" for the list.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/commands/config.js
CHANGED
|
@@ -51,8 +51,8 @@ export async function setFunnel(id, _flags, via = 'auth config') {
|
|
|
51
51
|
if (!orgId)
|
|
52
52
|
error('No default organization set. Run: myapi auth config set-org <id>');
|
|
53
53
|
info('› Validating…');
|
|
54
|
-
const
|
|
55
|
-
const funnelId =
|
|
54
|
+
const { funnel } = await sdkFunnel.getFunnel(config.api_key, orgId, id);
|
|
55
|
+
const funnelId = funnel.id;
|
|
56
56
|
config.default_funnel = funnelId;
|
|
57
57
|
saveConfig(config);
|
|
58
58
|
success(`Default funnel set to: ${funnelId}`);
|
|
@@ -10,6 +10,7 @@ export const EXPOSES = [
|
|
|
10
10
|
'GET /crm/orgs/{org_id}/companies/{id}',
|
|
11
11
|
'PATCH /crm/orgs/{org_id}/companies/{id}',
|
|
12
12
|
'DELETE /crm/orgs/{org_id}/companies/{id}',
|
|
13
|
+
'POST /crm/orgs/{org_id}/companies/{id}/restore',
|
|
13
14
|
];
|
|
14
15
|
export const SCHEMA = {};
|
|
15
16
|
function csv(v) {
|
|
@@ -10,6 +10,7 @@ export const EXPOSES = [
|
|
|
10
10
|
'GET /crm/orgs/{org_id}/contacts/{id}',
|
|
11
11
|
'PATCH /crm/orgs/{org_id}/contacts/{id}',
|
|
12
12
|
'DELETE /crm/orgs/{org_id}/contacts/{id}',
|
|
13
|
+
'POST /crm/orgs/{org_id}/contacts/{id}/restore',
|
|
13
14
|
'GET /crm/orgs/{org_id}/contacts/{id}/events',
|
|
14
15
|
];
|
|
15
16
|
export const SCHEMA = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Unit tests for the doctor's `setup` section augmentation (2026-06-05).
|
|
2
|
+
// Exercises the re-interpretation of backend sections — when the
|
|
3
|
+
// `domains` or `emails` sections come back empty (zero-state), the
|
|
4
|
+
// `setup` augmentation should escalate to actionable warns.
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { _setupSection } from './doctor.js';
|
|
7
|
+
function mkReport(sections) {
|
|
8
|
+
return {
|
|
9
|
+
org_id: 'org-test',
|
|
10
|
+
generated_at: '2026-06-05T00:00:00Z',
|
|
11
|
+
sections,
|
|
12
|
+
totals: { ok: 0, warn: 0, crit: 0 },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function empty(name) {
|
|
16
|
+
return { name, summary: 'no resources', issues: [] };
|
|
17
|
+
}
|
|
18
|
+
function populated(name) {
|
|
19
|
+
return { name, summary: 'all checks passed', issues: [{
|
|
20
|
+
id: 'x', severity: 'ok', scope: name, message: 'something',
|
|
21
|
+
}] };
|
|
22
|
+
}
|
|
23
|
+
describe('_setupSection', () => {
|
|
24
|
+
it('returns null when both domains + emails sections are populated', () => {
|
|
25
|
+
expect(_setupSection(mkReport([populated('domains'), populated('emails')]))).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
it('emits both warns when both sections are empty', () => {
|
|
28
|
+
const s = _setupSection(mkReport([empty('domains'), empty('emails')]));
|
|
29
|
+
expect(s).not.toBeNull();
|
|
30
|
+
expect(s.issues.map(i => i.severity)).toEqual(['warn', 'warn']);
|
|
31
|
+
expect(s.issues[0].message).toMatch(/No domain registered/);
|
|
32
|
+
expect(s.issues[1].message).toMatch(/No email inbox configured/);
|
|
33
|
+
expect(s.summary).toBe('2 setup gaps');
|
|
34
|
+
});
|
|
35
|
+
it('emits one warn when only emails is empty', () => {
|
|
36
|
+
const s = _setupSection(mkReport([populated('domains'), empty('emails')]));
|
|
37
|
+
expect(s).not.toBeNull();
|
|
38
|
+
expect(s.issues).toHaveLength(1);
|
|
39
|
+
expect(s.issues[0].scope).toBe('setup/email-inbox');
|
|
40
|
+
expect(s.summary).toBe('1 setup gap');
|
|
41
|
+
});
|
|
42
|
+
it('emits one warn when only domains is empty', () => {
|
|
43
|
+
const s = _setupSection(mkReport([empty('domains'), populated('emails')]));
|
|
44
|
+
expect(s).not.toBeNull();
|
|
45
|
+
expect(s.issues).toHaveLength(1);
|
|
46
|
+
expect(s.issues[0].scope).toBe('setup/domain');
|
|
47
|
+
});
|
|
48
|
+
it('returns null when the backend omits both sections entirely', () => {
|
|
49
|
+
// If a future backend drops these sections, we shouldn't fabricate
|
|
50
|
+
// warns out of nothing. Silence is the safe default.
|
|
51
|
+
expect(_setupSection(mkReport([populated('funnels')]))).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
it('attaches actionable hints', () => {
|
|
54
|
+
const s = _setupSection(mkReport([empty('domains'), empty('emails')]));
|
|
55
|
+
expect(s.issues[0].hint).toMatch(/myapi domain register/);
|
|
56
|
+
expect(s.issues[1].hint).toMatch(/myapi email mailbox create/);
|
|
57
|
+
});
|
|
58
|
+
it('emits a mailing-address warn when ctx.mailingAddress is null', () => {
|
|
59
|
+
const s = _setupSection(mkReport([populated('domains'), populated('emails')]), { mailingAddress: null });
|
|
60
|
+
expect(s).not.toBeNull();
|
|
61
|
+
expect(s.issues).toHaveLength(1);
|
|
62
|
+
expect(s.issues[0].scope).toBe('setup/mailing-address');
|
|
63
|
+
expect(s.issues[0].hint).toMatch(/myapi account mailing-address/);
|
|
64
|
+
});
|
|
65
|
+
it('does NOT emit mailing-address warn when set', () => {
|
|
66
|
+
const s = _setupSection(mkReport([populated('domains'), populated('emails')]), { mailingAddress: '1 Test St, Paris' });
|
|
67
|
+
expect(s).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
it('stays silent when mailing-address fetch failed (undefined)', () => {
|
|
70
|
+
// undefined = "unknown, infra blip" — don't false-alarm.
|
|
71
|
+
const s = _setupSection(mkReport([populated('domains'), populated('emails')]), { mailingAddress: undefined });
|
|
72
|
+
expect(s).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { hq as sdkHq } from '@myapihq/sdk';
|
|
1
2
|
import type { FlagSchema } from '../flags.js';
|
|
2
3
|
import { type Flags } from '../helpers.js';
|
|
3
4
|
import type { Exposes } from '../exposes.js';
|
|
4
5
|
export declare const EXPOSES: Exposes;
|
|
5
6
|
export declare const SCHEMA: FlagSchema;
|
|
7
|
+
export interface SetupContext {
|
|
8
|
+
mailingAddress?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function _setupSection(report: sdkHq.DoctorReport, ctx?: SetupContext): sdkHq.DoctorSection | null;
|
|
6
11
|
export declare function run(_subcommand: string | undefined, _args: string[], flags: Flags): Promise<void>;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -27,6 +27,8 @@ export const EXPOSES = [
|
|
|
27
27
|
'GET /container/orgs/{org_id}/containers',
|
|
28
28
|
'GET /funnel/orgs/{org_id}/funnels',
|
|
29
29
|
'GET /funnel/orgs/{org_id}/funnels/{funnel_id}/pages',
|
|
30
|
+
// Best-effort read for the setup-gap mailing_address check (CAN-SPAM).
|
|
31
|
+
'GET /hq/account/mailing-address',
|
|
30
32
|
];
|
|
31
33
|
export const SCHEMA = {};
|
|
32
34
|
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
@@ -58,6 +60,54 @@ function fmtIssue(i) {
|
|
|
58
60
|
const head = ` ${MARK[i.severity] ?? '·'} ${label}${i.message}`;
|
|
59
61
|
return i.hint ? `${head}\n ${C.dim}→ ${i.hint}${C.reset}` : head;
|
|
60
62
|
}
|
|
63
|
+
export function _setupSection(report, ctx = {}) {
|
|
64
|
+
const byName = new Map();
|
|
65
|
+
for (const s of report.sections)
|
|
66
|
+
byName.set(s.name, s);
|
|
67
|
+
const issues = [];
|
|
68
|
+
const dom = byName.get('domains');
|
|
69
|
+
if (dom && dom.issues.length === 0) {
|
|
70
|
+
issues.push({
|
|
71
|
+
id: localIssueId('setup_no_domain', report.org_id),
|
|
72
|
+
severity: 'warn',
|
|
73
|
+
scope: 'setup/domain',
|
|
74
|
+
category: 'setup',
|
|
75
|
+
message: 'No domain registered for this org',
|
|
76
|
+
hint: 'Register one with: myapi domain register <domain> && myapi domain assign <domain>',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const em = byName.get('emails');
|
|
80
|
+
if (em && em.issues.length === 0) {
|
|
81
|
+
issues.push({
|
|
82
|
+
id: localIssueId('setup_no_mailbox', report.org_id),
|
|
83
|
+
severity: 'warn',
|
|
84
|
+
scope: 'setup/email-inbox',
|
|
85
|
+
category: 'setup',
|
|
86
|
+
message: 'No email inbox configured',
|
|
87
|
+
hint: 'Create one with: myapi email mailbox create <username>@<your-domain>',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Account-scoped (so use report.org_id only for the dedup id, not as
|
|
91
|
+
// entity scope). Hard-gates every transactional `email send` —
|
|
92
|
+
// backend returns MAILING_ADDRESS_REQUIRED until set.
|
|
93
|
+
if (ctx.mailingAddress === null) {
|
|
94
|
+
issues.push({
|
|
95
|
+
id: localIssueId('setup_no_mailing_address', report.org_id),
|
|
96
|
+
severity: 'warn',
|
|
97
|
+
scope: 'setup/mailing-address',
|
|
98
|
+
category: 'setup',
|
|
99
|
+
message: 'Account has no mailing address (CAN-SPAM)',
|
|
100
|
+
hint: 'Set one with: myapi account mailing-address "<full postal address>"',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (issues.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
name: 'setup',
|
|
107
|
+
summary: `${issues.length} setup gap${issues.length === 1 ? '' : 's'}`,
|
|
108
|
+
issues,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
61
111
|
// Local DNS-resolution probe for every distinct domain the report names.
|
|
62
112
|
// The backend can verify domain provisioning state from its own egress;
|
|
63
113
|
// this checks whether the operator's network reaches them today — a
|
|
@@ -207,7 +257,26 @@ export async function run(_subcommand, _args, flags) {
|
|
|
207
257
|
error(`doctor endpoint failed: ${e?.message ?? String(e)}`);
|
|
208
258
|
}
|
|
209
259
|
// Local augmentation, appended after the backend's sections by design —
|
|
210
|
-
// see the file header on why client-observed checks stay grouped at the
|
|
260
|
+
// see the file header on why client-observed checks stay grouped at the
|
|
261
|
+
// end. `setup` re-interprets the backend's own sections (zero-state →
|
|
262
|
+
// warn) and comes first; `local network` + `reachability` are liveness
|
|
263
|
+
// probes that follow.
|
|
264
|
+
//
|
|
265
|
+
// Best-effort fetch the account-scoped mailing_address — `null` if
|
|
266
|
+
// unset, `undefined` if the call fails (transient / permissions / 404
|
|
267
|
+
// on older backends). `null` triggers the warn; `undefined` is silent
|
|
268
|
+
// so we don't false-alarm on infra blips.
|
|
269
|
+
let mailingAddress;
|
|
270
|
+
try {
|
|
271
|
+
const res = await sdkHq.getMailingAddress(apiKey);
|
|
272
|
+
mailingAddress = res?.mailing_address ?? null;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
mailingAddress = undefined;
|
|
276
|
+
}
|
|
277
|
+
const setup = _setupSection(report, { mailingAddress });
|
|
278
|
+
if (setup)
|
|
279
|
+
report.sections.push(setup);
|
|
211
280
|
const localSection = await dnsProbeSection(report);
|
|
212
281
|
if (localSection)
|
|
213
282
|
report.sections.push(localSection);
|
|
@@ -266,6 +335,9 @@ Sections returned by the backend today:
|
|
|
266
335
|
funnels, webhooks, workflows, domains, containers, emails, payments
|
|
267
336
|
|
|
268
337
|
Local additions:
|
|
338
|
+
setup — Flags zero-state config the backend currently reports as
|
|
339
|
+
"no resources": no domain registered, no email inbox.
|
|
340
|
+
Warnings, not critical — the org may not need them yet.
|
|
269
341
|
local network — DNS resolution from your egress for each domain mentioned.
|
|
270
342
|
reachability — HTTP GET to every container URL and funnel page; surfaces
|
|
271
343
|
the status code. Failures are warnings, never critical —
|
|
@@ -13,6 +13,7 @@ export declare function unassign(domainArg: string, flags: Flags): Promise<void>
|
|
|
13
13
|
export declare function status(domainArg: string, flags: Flags): Promise<void>;
|
|
14
14
|
export declare function emailSetup(domainArg: string, flags: Flags): Promise<void>;
|
|
15
15
|
export declare function retryProvisioning(domainArg: string, flags: Flags): Promise<void>;
|
|
16
|
+
export declare function mailServerResync(domainArg: string, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function settings(domainArg: string, flags: Flags): Promise<void>;
|
|
17
18
|
export declare function updateSettings(domainArg: string, flags: Flags): Promise<void>;
|
|
18
19
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
package/dist/commands/domain.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { domain as sdkDomain } from '@myapihq/sdk';
|
|
1
|
+
import { domain as sdkDomain, email as sdkEmail } from '@myapihq/sdk';
|
|
2
2
|
import { requireConfig } from '../config.js';
|
|
3
3
|
import { success, error, info, printTable, printJson } from '../output.js';
|
|
4
4
|
import { formatDate } from '../utils.js';
|
|
@@ -16,6 +16,12 @@ export const EXPOSES = [
|
|
|
16
16
|
'GET /domain/orgs/{org_id}/{domain}/status',
|
|
17
17
|
'POST /domain/orgs/{org_id}/{domain}/email-infra',
|
|
18
18
|
'POST /domain/orgs/{org_id}/{domain}/retry-provisioning',
|
|
19
|
+
// Mail-server (Stalwart) reprovisioning — distinct from DNS/cert
|
|
20
|
+
// retry-provisioning above. Call when mailbox-create returns
|
|
21
|
+
// DOMAIN_NOT_MAIL_READY / MAILBOX_PROVISION_FAILED, or when inbox
|
|
22
|
+
// reads return "mail server error" on a domain whose
|
|
23
|
+
// email_infra_ready=true.
|
|
24
|
+
'POST /email/orgs/{org_id}/domains/{domain}/mail-server-resync',
|
|
19
25
|
'GET /domain/orgs/{org_id}/{domain}/records',
|
|
20
26
|
'POST /domain/orgs/{org_id}/{domain}/records',
|
|
21
27
|
'GET /domain/orgs/{org_id}/{domain}/records/{record_id}',
|
|
@@ -312,6 +318,24 @@ export async function retryProvisioning(domainArg, flags) {
|
|
|
312
318
|
info(res.next_step);
|
|
313
319
|
info(`Track with: myapi domain status ${domain} --watch`);
|
|
314
320
|
}
|
|
321
|
+
// Re-runs mail-server (Stalwart) provisioning for a domain. Distinct from
|
|
322
|
+
// retry-provisioning above — that re-runs the DNS / cert side; this one
|
|
323
|
+
// re-runs the IMAP/SMTP / mail-store side. Idempotent. Use when:
|
|
324
|
+
// - `email mailbox create` returns DOMAIN_NOT_MAIL_READY / MAILBOX_PROVISION_FAILED
|
|
325
|
+
// - `email message inbox <addr>` returns "mail server error" on a domain
|
|
326
|
+
// whose `email_infra_ready=true` according to `domain list`.
|
|
327
|
+
export async function mailServerResync(domainArg, flags) {
|
|
328
|
+
const config = requireConfig();
|
|
329
|
+
const orgId = requireOrg(flags, config, 'myapi domain mail-server-resync <domain> [--org <id>]');
|
|
330
|
+
const domain = requireDomain(domainArg, flags, config, 'myapi domain mail-server-resync <domain> [--org <id>]');
|
|
331
|
+
const res = await sdkEmail.mailServerResync(config.api_key, orgId, domain);
|
|
332
|
+
if (flags.json) {
|
|
333
|
+
printJson(res);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
success(`Mail-server resync triggered for ${domain}`);
|
|
337
|
+
info('Retry mailbox-create / inbox reads in a few seconds.');
|
|
338
|
+
}
|
|
315
339
|
export async function settings(domainArg, flags) {
|
|
316
340
|
const config = requireConfig();
|
|
317
341
|
const orgId = requireOrg(flags, config, 'myapi domain settings <domain> [--org <id>]');
|
|
@@ -591,6 +615,17 @@ Re-runs domain provisioning when status=infra_error and error_detail.retryable=t
|
|
|
591
615
|
The CLI surfaces the retry hint automatically when status renders infra_error.
|
|
592
616
|
|
|
593
617
|
After triggering retry, poll: myapi domain status <domain> --watch`,
|
|
618
|
+
'mail-server-resync': `myapi domain mail-server-resync <domain> [--org <id>]
|
|
619
|
+
|
|
620
|
+
Re-runs the mail-server (Stalwart) provisioning for a domain. Distinct
|
|
621
|
+
from "retry-provisioning" — that re-runs the DNS / cert side; this one
|
|
622
|
+
re-runs the IMAP / SMTP / mail-store side. Idempotent.
|
|
623
|
+
|
|
624
|
+
Use when:
|
|
625
|
+
- "email mailbox create" returns DOMAIN_NOT_MAIL_READY or MAILBOX_PROVISION_FAILED
|
|
626
|
+
- "email message inbox <addr>" returns "mail server error" but
|
|
627
|
+
"domain list" shows email_infra_ready=true for that domain
|
|
628
|
+
- The fix-path called out in backend-email-postdeploy-findings-2026-06-05`,
|
|
594
629
|
'update-settings': `myapi domain update-settings <domain> [--security=<level>] [--browser-check=on|off] [--purge-cache] [--org <id>]
|
|
595
630
|
|
|
596
631
|
--security=<level> essentially_off | low | medium | high | under_attack (default: medium)
|
|
@@ -610,6 +645,7 @@ Subcommands:
|
|
|
610
645
|
email-setup Opt in to MyAPI-managed email on a subdomain (default: mail.<domain>)
|
|
611
646
|
import Bring your own domain (BYOD) — registrar-agnostic
|
|
612
647
|
list List domains
|
|
648
|
+
mail-server-resync Re-run Stalwart mail-server provisioning for a domain (fixes inbox/create on broken domains)
|
|
613
649
|
records Manage DNS records in the zone (list / get / create / update / delete)
|
|
614
650
|
register Register a domain
|
|
615
651
|
renew Renew a registered domain for another period
|
|
@@ -643,6 +679,7 @@ Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to sk
|
|
|
643
679
|
case 'update-settings': return updateSettings(args[0], flags);
|
|
644
680
|
case 'records': return recordsRun(args[0], args.slice(1), flags);
|
|
645
681
|
case 'email-setup': return emailSetup(args[0], flags);
|
|
682
|
+
case 'mail-server-resync': return mailServerResync(args[0], flags);
|
|
646
683
|
case 'retry-provisioning': return retryProvisioning(args[0], flags);
|
|
647
684
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi domain --help" for a list of valid subcommands.`);
|
|
648
685
|
}
|
|
@@ -4,6 +4,7 @@ import { success, error, printTable, info } from '../../output.js';
|
|
|
4
4
|
export const EXPOSES = [
|
|
5
5
|
'POST /email/mailboxes/create',
|
|
6
6
|
'GET /email/mailboxes',
|
|
7
|
+
'DELETE /email/mailboxes/{address}',
|
|
7
8
|
'PUT /email/mailboxes/{address}/forwarding',
|
|
8
9
|
'DELETE /email/mailboxes/{address}/forwarding',
|
|
9
10
|
'POST /email/sending/activate',
|
|
@@ -70,6 +71,17 @@ async function clearForwarding(address, _flags) {
|
|
|
70
71
|
await sdkEmail.deleteForwarding(config.api_key, address);
|
|
71
72
|
success(`Forwarding cleared for ${address}`);
|
|
72
73
|
}
|
|
74
|
+
async function del(address, _flags) {
|
|
75
|
+
const config = requireConfig();
|
|
76
|
+
if (!address)
|
|
77
|
+
error('Missing required arguments.\nUsage: myapi email mailbox delete <user@domain>');
|
|
78
|
+
// Backend is idempotent — 204 whether or not the mailbox existed. So we
|
|
79
|
+
// don't pre-check existence; just call delete and report success. The
|
|
80
|
+
// 409 MAILBOX_IN_USE case bubbles up as an API error with a clear
|
|
81
|
+
// pointer to pause the campaign first.
|
|
82
|
+
await sdkEmail.deleteMailbox(config.api_key, address);
|
|
83
|
+
success(`Mailbox deleted: ${address}`);
|
|
84
|
+
}
|
|
73
85
|
const USAGE = {
|
|
74
86
|
'create': `myapi email mailbox create <user@domain> [--display-name <name>]
|
|
75
87
|
myapi email mailbox create --username <u> --domain <d> [--display-name <name>]
|
|
@@ -83,6 +95,11 @@ matches the rest of the CLI ("first required arg is positional").`,
|
|
|
83
95
|
org (orphaned mailboxes — domain was unassigned
|
|
84
96
|
without first deleting them).`,
|
|
85
97
|
'activate-sending': 'myapi email mailbox activate-sending --address <email>',
|
|
98
|
+
'delete': `myapi email mailbox delete <user@domain>
|
|
99
|
+
|
|
100
|
+
Idempotent — returns success whether or not the mailbox existed.
|
|
101
|
+
Refuses with MAILBOX_IN_USE when an active or paused campaign still
|
|
102
|
+
sends from this address; pause/delete the campaign first.`,
|
|
86
103
|
'set-forwarding': `myapi email mailbox set-forwarding <user@domain> <forward-to@domain>
|
|
87
104
|
|
|
88
105
|
Redirects a copy of every incoming message to an external address
|
|
@@ -97,6 +114,7 @@ Subcommands:
|
|
|
97
114
|
activate-sending Activate outbound sending for a mailbox
|
|
98
115
|
clear-forwarding Stop forwarding for a mailbox
|
|
99
116
|
create Create a mailbox (positional <user@domain> or --username/--domain)
|
|
117
|
+
delete Delete a mailbox (idempotent; rejects if a campaign still uses it)
|
|
100
118
|
list List mailboxes on a domain (--domain) or orphaned ones (--filter unassigned)
|
|
101
119
|
set-forwarding Forward a copy of incoming mail to an external address`);
|
|
102
120
|
return;
|
|
@@ -112,6 +130,7 @@ Subcommands:
|
|
|
112
130
|
switch (sub) {
|
|
113
131
|
case 'create': return create(args[0], flags);
|
|
114
132
|
case 'list': return list(flags);
|
|
133
|
+
case 'delete': return del(args[0], flags);
|
|
115
134
|
case 'activate-sending': return activateSending(flags);
|
|
116
135
|
case 'set-forwarding': return setForwarding(args[0], args[1], flags);
|
|
117
136
|
case 'clear-forwarding': return clearForwarding(args[0], flags);
|
|
@@ -9,6 +9,7 @@ export declare function get(id: string, flags: Flags): Promise<void>;
|
|
|
9
9
|
export declare function del(id: string, flags: Flags): Promise<void>;
|
|
10
10
|
export declare function pages(funnelArg: string, flags: Flags): Promise<void>;
|
|
11
11
|
export declare function push(slug: string, flags: Flags): Promise<void>;
|
|
12
|
+
export declare function formCmd(funnelArg: string | undefined, flags: Flags): Promise<void>;
|
|
12
13
|
export declare function verify(slug: string, flags: Flags): Promise<void>;
|
|
13
14
|
export declare function publish(dir: string, flags: Flags): Promise<void>;
|
|
14
15
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|