@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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// `myapi crm <namespace> <subcommand>` — Two namespaces (contacts +
|
|
2
|
+
// companies) each with the same shape: list / search / get / create /
|
|
3
|
+
// update / delete / restore / promote. Contacts also have `events` to
|
|
4
|
+
// inspect the timeline.
|
|
5
|
+
import { error, info } from '../../output.js';
|
|
6
|
+
import * as contacts from './contacts.js';
|
|
7
|
+
import * as companies from './companies.js';
|
|
8
|
+
// Dispatcher exports an empty EXPOSES; each subcommand file declares its own.
|
|
9
|
+
export const EXPOSES = [];
|
|
10
|
+
export const SCHEMA = {
|
|
11
|
+
org: 'string',
|
|
12
|
+
// Shared filter/payload flags across contacts + companies
|
|
13
|
+
email: 'string',
|
|
14
|
+
domain: 'string',
|
|
15
|
+
'first-name': 'string',
|
|
16
|
+
'last-name': 'string',
|
|
17
|
+
name: 'string',
|
|
18
|
+
stage: 'string',
|
|
19
|
+
source: 'string',
|
|
20
|
+
'company-id': 'string',
|
|
21
|
+
'custom-json': 'string',
|
|
22
|
+
'include-deleted': 'boolean',
|
|
23
|
+
'min-last-engagement-days': 'number',
|
|
24
|
+
'max-last-engagement-days': 'number',
|
|
25
|
+
limit: 'number',
|
|
26
|
+
offset: 'number',
|
|
27
|
+
cursor: 'string',
|
|
28
|
+
kind: 'string',
|
|
29
|
+
};
|
|
30
|
+
async function dispatchNamespace(ns, sub, rest, flags) {
|
|
31
|
+
switch (ns) {
|
|
32
|
+
case 'contacts': return contacts.run(sub, rest, flags);
|
|
33
|
+
case 'companies': return companies.run(sub, rest, flags);
|
|
34
|
+
default: error(`Unknown namespace: ${ns}. Run "myapi crm --help" for the list.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function run(subcommand, args, flags) {
|
|
38
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
39
|
+
info(`Usage: myapi crm <namespace> <subcommand>
|
|
40
|
+
|
|
41
|
+
Namespaces:
|
|
42
|
+
contacts The people you've engaged with (private to org)
|
|
43
|
+
companies The companies you've engaged with (private to org)
|
|
44
|
+
|
|
45
|
+
Each namespace shares the same subcommands:
|
|
46
|
+
list List with no filter (alias for search)
|
|
47
|
+
search Filter by stage / source / email / domain / engagement
|
|
48
|
+
get <id> Fetch one
|
|
49
|
+
create <email|domain> ... Manually create
|
|
50
|
+
update <id> ... Patch fields (stage, names, custom JSON)
|
|
51
|
+
delete <id> Soft delete (events retained)
|
|
52
|
+
restore <id> Restore a soft-deleted row
|
|
53
|
+
promote <goldfox_id> Promote a Goldfox lead into your CRM
|
|
54
|
+
|
|
55
|
+
Contacts also have:
|
|
56
|
+
events <id> [--kind <k>] Inspect the contact's engagement timeline
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
myapi crm contacts list
|
|
60
|
+
myapi crm contacts search --stage qualified --source goldfox
|
|
61
|
+
myapi crm contacts promote p_MC0xMDUuY29t.0
|
|
62
|
+
myapi crm contacts update <id> --stage qualified
|
|
63
|
+
myapi crm contacts events <id> --kind webhook_received
|
|
64
|
+
|
|
65
|
+
Auto-ingest: webhooks with a configurable email path land here as
|
|
66
|
+
\`webhook_received\` events on a contact automatically. Email and pixel
|
|
67
|
+
ingest are coming — see "myapi crm --help" later for the wiring.
|
|
68
|
+
|
|
69
|
+
Org-scoped: all commands accept --org <id> (or set default: myapi config set-org <id>).
|
|
70
|
+
Run "myapi crm <namespace> --help" for namespace-specific commands.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return dispatchNamespace(subcommand, args[0], args.slice(1), flags);
|
|
74
|
+
}
|
|
@@ -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,259 @@
|
|
|
1
|
+
// `myapi database <subcommand>` — KV primitive with per-org namespaces.
|
|
2
|
+
// Two resource groups: namespaces (container) and keys (the values).
|
|
3
|
+
// Surface kept flat (not nested under `database namespaces ... keys ...`)
|
|
4
|
+
// because most calls are key-level and we want tab-completion to land in
|
|
5
|
+
// one hop.
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import { database as sdk, MyApiError } from '@myapihq/sdk';
|
|
8
|
+
import { requireConfig } from '../config.js';
|
|
9
|
+
import { success, error, info, printTable, printJson } from '../output.js';
|
|
10
|
+
import { requireOrg, requireArg } from '../helpers.js';
|
|
11
|
+
export const EXPOSES = [
|
|
12
|
+
'GET /database/orgs/{org_id}/namespaces',
|
|
13
|
+
'POST /database/orgs/{org_id}/namespaces',
|
|
14
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}',
|
|
15
|
+
'DELETE /database/orgs/{org_id}/namespaces/{ns}',
|
|
16
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}/keys',
|
|
17
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
18
|
+
'PUT /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
19
|
+
'DELETE /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
20
|
+
];
|
|
21
|
+
export const SCHEMA = {
|
|
22
|
+
org: 'string',
|
|
23
|
+
ns: 'string',
|
|
24
|
+
prefix: 'string',
|
|
25
|
+
cursor: 'string',
|
|
26
|
+
limit: 'number',
|
|
27
|
+
values: 'boolean',
|
|
28
|
+
file: 'string',
|
|
29
|
+
'if-match': 'string',
|
|
30
|
+
};
|
|
31
|
+
// `--values` for list-keys requires both `--values` and (for now) `=true`. We
|
|
32
|
+
// accept the bare flag too because that's how every other boolean works.
|
|
33
|
+
function wantValues(flags) {
|
|
34
|
+
return flags.values === true || flags.values === 'true';
|
|
35
|
+
}
|
|
36
|
+
// Resolve --ns flag. Required for every key-level op.
|
|
37
|
+
function requireNs(flags, usage) {
|
|
38
|
+
const ns = typeof flags.ns === 'string' ? flags.ns : '';
|
|
39
|
+
if (!ns)
|
|
40
|
+
error(`Missing required flag: --ns <namespace>.\nUsage: ${usage}`);
|
|
41
|
+
return ns;
|
|
42
|
+
}
|
|
43
|
+
// Read a JSON value from one of: positional arg, --file, or stdin ('-').
|
|
44
|
+
function readJsonValue(arg, flags, usage) {
|
|
45
|
+
let raw;
|
|
46
|
+
if (typeof flags.file === 'string' && flags.file) {
|
|
47
|
+
raw = fs.readFileSync(flags.file, 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
else if (arg === '-') {
|
|
50
|
+
raw = fs.readFileSync(0, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
else if (arg !== undefined) {
|
|
53
|
+
raw = arg;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
error(`Missing required argument <value>.\nUsage: ${usage}`);
|
|
57
|
+
}
|
|
58
|
+
raw = raw.trim();
|
|
59
|
+
if (!raw)
|
|
60
|
+
error('Empty value.');
|
|
61
|
+
// Pre-flight size check — server caps at 256 KB.
|
|
62
|
+
if (Buffer.byteLength(raw, 'utf-8') > sdk.MAX_VALUE_BYTES) {
|
|
63
|
+
error(`Value exceeds ${sdk.MAX_VALUE_BYTES / 1024} KB limit. Use myapi storage for blobs.`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
error(`Value is not valid JSON: ${e.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ── Namespaces ───────────────────────────────────────────────────────────
|
|
73
|
+
async function nsList(flags) {
|
|
74
|
+
const config = requireConfig();
|
|
75
|
+
const orgId = requireOrg(flags, config, 'myapi database namespaces [--org <id>]');
|
|
76
|
+
const res = await sdk.listNamespaces(config.api_key, orgId);
|
|
77
|
+
if (flags.json) {
|
|
78
|
+
printJson(res);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
printTable(res.namespaces.map(n => ({
|
|
82
|
+
name: n.name,
|
|
83
|
+
keys: n.key_count,
|
|
84
|
+
created: n.created_at,
|
|
85
|
+
})), { flags, empty: 'No namespaces yet. Create one: myapi database create <name>' });
|
|
86
|
+
}
|
|
87
|
+
async function nsCreate(name, flags) {
|
|
88
|
+
const config = requireConfig();
|
|
89
|
+
const orgId = requireOrg(flags, config, 'myapi database create <name> [--org <id>]');
|
|
90
|
+
requireArg(name, 'name', 'myapi database create <name>');
|
|
91
|
+
const ns = await sdk.createNamespace(config.api_key, orgId, name);
|
|
92
|
+
if (flags.json) {
|
|
93
|
+
printJson(ns);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
success(`Namespace "${ns.name}" created.`);
|
|
97
|
+
}
|
|
98
|
+
async function nsDelete(name, flags) {
|
|
99
|
+
const config = requireConfig();
|
|
100
|
+
const orgId = requireOrg(flags, config, 'myapi database delete-namespace <name> [--org <id>]');
|
|
101
|
+
requireArg(name, 'name', 'myapi database delete-namespace <name>');
|
|
102
|
+
await sdk.deleteNamespace(config.api_key, orgId, name);
|
|
103
|
+
success(`Namespace "${name}" and all its keys deleted.`);
|
|
104
|
+
}
|
|
105
|
+
// ── Keys ─────────────────────────────────────────────────────────────────
|
|
106
|
+
async function keysList(flags) {
|
|
107
|
+
const config = requireConfig();
|
|
108
|
+
const orgId = requireOrg(flags, config, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>]');
|
|
109
|
+
const ns = requireNs(flags, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values]');
|
|
110
|
+
const opts = {};
|
|
111
|
+
if (typeof flags.prefix === 'string')
|
|
112
|
+
opts.prefix = flags.prefix;
|
|
113
|
+
if (typeof flags.limit === 'number')
|
|
114
|
+
opts.limit = flags.limit;
|
|
115
|
+
if (typeof flags.cursor === 'string')
|
|
116
|
+
opts.cursor = flags.cursor;
|
|
117
|
+
if (wantValues(flags))
|
|
118
|
+
opts.values = true;
|
|
119
|
+
const res = await sdk.listKeys(config.api_key, orgId, ns, opts);
|
|
120
|
+
if (flags.json) {
|
|
121
|
+
printJson(res);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (res.keys.length === 0) {
|
|
125
|
+
info('No keys.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (wantValues(flags)) {
|
|
129
|
+
for (const k of res.keys) {
|
|
130
|
+
info(`${k.key} (etag=${k.etag})`);
|
|
131
|
+
info(` ${JSON.stringify(k.value)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
for (const k of res.keys)
|
|
136
|
+
info(k.key);
|
|
137
|
+
}
|
|
138
|
+
if (res.next_cursor)
|
|
139
|
+
info(`\n— More results — pass --cursor "${res.next_cursor}" to continue`);
|
|
140
|
+
}
|
|
141
|
+
async function keyGet(key, flags) {
|
|
142
|
+
const config = requireConfig();
|
|
143
|
+
const orgId = requireOrg(flags, config, 'myapi database get <key> --ns <ns> [--org <id>]');
|
|
144
|
+
const ns = requireNs(flags, 'myapi database get <key> --ns <ns>');
|
|
145
|
+
requireArg(key, 'key', 'myapi database get <key> --ns <ns>');
|
|
146
|
+
const entry = await sdk.getKey(config.api_key, orgId, ns, key);
|
|
147
|
+
if (flags.json) {
|
|
148
|
+
printJson(entry);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
info(JSON.stringify(entry.value));
|
|
152
|
+
process.stderr.write(`\n— etag=${entry.etag} · updated=${entry.updated_at}\n`);
|
|
153
|
+
}
|
|
154
|
+
async function keySet(key, valueArg, flags) {
|
|
155
|
+
const config = requireConfig();
|
|
156
|
+
const orgId = requireOrg(flags, config, 'myapi database set <key> <value-json> --ns <ns> [--if-match <etag>] [--file <path>] [--org <id>]');
|
|
157
|
+
const ns = requireNs(flags, 'myapi database set <key> <value-json> --ns <ns>');
|
|
158
|
+
requireArg(key, 'key', 'myapi database set <key> <value-json>');
|
|
159
|
+
const value = readJsonValue(valueArg, flags, 'myapi database set <key> <value-json> --ns <ns>');
|
|
160
|
+
const ifMatch = typeof flags['if-match'] === 'string' ? flags['if-match'] : undefined;
|
|
161
|
+
try {
|
|
162
|
+
const entry = await sdk.putKey(config.api_key, orgId, ns, key, value, ifMatch);
|
|
163
|
+
if (flags.json) {
|
|
164
|
+
printJson(entry);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
success(`Set ${ns}:${key} (etag=${entry.etag})`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
if (err instanceof MyApiError && err.code === 'ETAG_MISMATCH') {
|
|
171
|
+
error('etag mismatch — another writer updated this key. Re-read with: myapi database get');
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function keyDel(key, flags) {
|
|
177
|
+
const config = requireConfig();
|
|
178
|
+
const orgId = requireOrg(flags, config, 'myapi database del <key> --ns <ns> [--if-match <etag>] [--org <id>]');
|
|
179
|
+
const ns = requireNs(flags, 'myapi database del <key> --ns <ns>');
|
|
180
|
+
requireArg(key, 'key', 'myapi database del <key>');
|
|
181
|
+
const ifMatch = typeof flags['if-match'] === 'string' ? flags['if-match'] : undefined;
|
|
182
|
+
try {
|
|
183
|
+
await sdk.deleteKey(config.api_key, orgId, ns, key, ifMatch);
|
|
184
|
+
success(`Deleted ${ns}:${key}`);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
if (err instanceof MyApiError && err.code === 'ETAG_MISMATCH') {
|
|
188
|
+
error('etag mismatch — another writer updated this key. Re-read with: myapi database get');
|
|
189
|
+
}
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ── Dispatcher ───────────────────────────────────────────────────────────
|
|
194
|
+
const SUBCOMMAND_USAGE = {
|
|
195
|
+
'namespaces': 'myapi database namespaces [--org <id>] [--json]',
|
|
196
|
+
'create': 'myapi database create <name> [--org <id>]',
|
|
197
|
+
'delete-namespace': `myapi database delete-namespace <name> [--org <id>]
|
|
198
|
+
|
|
199
|
+
Deletes the namespace AND all keys in it. Irreversible.`,
|
|
200
|
+
'keys': `myapi database keys --ns <namespace> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>] [--json]
|
|
201
|
+
|
|
202
|
+
Default render is one key per line. --values inlines each value alongside
|
|
203
|
+
its key + etag. --cursor takes the next_cursor printed at the end of a
|
|
204
|
+
previous page.`,
|
|
205
|
+
'get': `myapi database get <key> --ns <namespace> [--org <id>] [--json]
|
|
206
|
+
|
|
207
|
+
Prints the value as compact JSON on stdout; etag + updated_at go to
|
|
208
|
+
stderr so pipelines like \`myapi database get foo --ns x | jq\` work.`,
|
|
209
|
+
'set': `myapi database set <key> <value-json> --ns <namespace> [--if-match <etag>] [--file <path>] [--org <id>]
|
|
210
|
+
|
|
211
|
+
<value-json> The value as a JSON literal (e.g. '{"foo":1}', '"bar"', 'true', '42')
|
|
212
|
+
--file <path> Read the value from a file instead of the positional arg
|
|
213
|
+
Pass "-" as <value-json> to read from stdin
|
|
214
|
+
--if-match Compare-and-swap. Pass the etag from a previous get; returns
|
|
215
|
+
error if another writer updated the key in the meantime.
|
|
216
|
+
|
|
217
|
+
Value must be valid JSON and ≤ 256 KB serialized. For blobs use myapi storage.`,
|
|
218
|
+
'del': `myapi database del <key> --ns <namespace> [--if-match <etag>] [--org <id>]
|
|
219
|
+
|
|
220
|
+
Delete a single key. CAS-protected via --if-match like set.`,
|
|
221
|
+
};
|
|
222
|
+
export async function run(subcommand, args, flags) {
|
|
223
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
224
|
+
info(`Usage: myapi database <subcommand>
|
|
225
|
+
|
|
226
|
+
Namespaces:
|
|
227
|
+
namespaces List namespaces in the org
|
|
228
|
+
create <name> Create a namespace
|
|
229
|
+
delete-namespace <name> Delete a namespace AND all its keys (irreversible)
|
|
230
|
+
|
|
231
|
+
Keys (require --ns <namespace>):
|
|
232
|
+
keys List keys in a namespace
|
|
233
|
+
get <key> Get a key's value + etag
|
|
234
|
+
set <key> <value-json> Set a key. Value is JSON; CAS via --if-match
|
|
235
|
+
del <key> Delete a key (CAS via --if-match)
|
|
236
|
+
|
|
237
|
+
All commands accept --org <id> (or set default: myapi config set-org <id>).
|
|
238
|
+
Values are JSON; per-key size cap is 256 KB. Use myapi storage for binary.`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (flags.help) {
|
|
242
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
243
|
+
if (usage)
|
|
244
|
+
info(`Usage: ${usage}`);
|
|
245
|
+
else
|
|
246
|
+
info(`Unknown subcommand: ${subcommand}. Run "myapi database --help" for the list.`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
switch (subcommand) {
|
|
250
|
+
case 'namespaces': return nsList(flags);
|
|
251
|
+
case 'create': return nsCreate(args[0], flags);
|
|
252
|
+
case 'delete-namespace': return nsDelete(args[0], flags);
|
|
253
|
+
case 'keys': return keysList(flags);
|
|
254
|
+
case 'get': return keyGet(args[0], flags);
|
|
255
|
+
case 'set': return keySet(args[0], args[1], flags);
|
|
256
|
+
case 'del': return keyDel(args[0], flags);
|
|
257
|
+
default: error(`Unknown subcommand: ${subcommand}. Run "myapi database --help" for valid subcommands.`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
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 check(domainArg: string, flags: Flags): Promise<void>;
|
|
5
7
|
export declare function register(domainArg: string, flags: Flags): Promise<void>;
|
|
8
|
+
export declare function renew(domainArg: string, flags: Flags): Promise<void>;
|
|
6
9
|
export declare function list(flags: Flags): Promise<void>;
|
|
7
10
|
export declare function assign(domainArg: string, flags: Flags): Promise<void>;
|
|
8
11
|
export declare function unassign(domainArg: string, flags: Flags): Promise<void>;
|
package/dist/commands/domain.js
CHANGED
|
@@ -2,7 +2,19 @@ import { domain as sdkDomain } 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';
|
|
5
|
+
import { resolveRegistrantForRegister } from '../registrant.js';
|
|
5
6
|
import { requireOrg, requireDomain } from '../helpers.js';
|
|
7
|
+
export const EXPOSES = [
|
|
8
|
+
'GET /domain/orgs/{org_id}/list',
|
|
9
|
+
'GET /domain/orgs/{org_id}/check/available/{name}',
|
|
10
|
+
'POST /domain/orgs/{org_id}/register',
|
|
11
|
+
'POST /domain/orgs/{org_id}/import',
|
|
12
|
+
'POST /domain/orgs/{org_id}/{domain}/assign',
|
|
13
|
+
'POST /domain/orgs/{org_id}/{domain}/renew',
|
|
14
|
+
'GET /domain/orgs/{org_id}/{domain}/settings',
|
|
15
|
+
'POST /domain/orgs/{org_id}/{domain}/settings',
|
|
16
|
+
'GET /domain/orgs/{org_id}/{domain}/status',
|
|
17
|
+
];
|
|
6
18
|
export const SCHEMA = {
|
|
7
19
|
org: 'string',
|
|
8
20
|
domain: 'string',
|
|
@@ -11,6 +23,19 @@ export const SCHEMA = {
|
|
|
11
23
|
security: 'string',
|
|
12
24
|
'browser-check': 'string',
|
|
13
25
|
'purge-cache': 'boolean',
|
|
26
|
+
// WHOIS registrant — required by ICANN at register time. Resolution
|
|
27
|
+
// order is JSON > per-field flags > stored config > TTY prompt > error.
|
|
28
|
+
// See packages/cli/src/registrant.ts for the full flow.
|
|
29
|
+
'registrant-json': 'string',
|
|
30
|
+
'registrant-name': 'string',
|
|
31
|
+
'registrant-email': 'string',
|
|
32
|
+
'registrant-phone': 'string',
|
|
33
|
+
'registrant-street': 'string',
|
|
34
|
+
'registrant-city': 'string',
|
|
35
|
+
'registrant-state': 'string',
|
|
36
|
+
'registrant-postal-code': 'string',
|
|
37
|
+
'registrant-country': 'string',
|
|
38
|
+
'registrant-organization': 'string',
|
|
14
39
|
};
|
|
15
40
|
export async function check(domainArg, flags) {
|
|
16
41
|
const config = requireConfig();
|
|
@@ -42,12 +67,26 @@ export async function register(domainArg, flags) {
|
|
|
42
67
|
if (!domainArg)
|
|
43
68
|
error('Missing required arguments.\nUsage: myapi domain register <domain> [--years <n>] [--org <id>]');
|
|
44
69
|
const years = flags.years || 1;
|
|
45
|
-
|
|
70
|
+
// Required by ICANN; resolver throws a useful error if nothing's set up
|
|
71
|
+
// in non-interactive contexts, or runs an interactive prompt for humans.
|
|
72
|
+
const registrant = await resolveRegistrantForRegister(flags, config);
|
|
73
|
+
await sdkDomain.registerDomain(config.api_key, orgId, domainArg, { years, registrant });
|
|
46
74
|
success(`Registered ${domainArg}!`);
|
|
47
75
|
info(`Assign it to your org now:\n myapi domain assign ${domainArg} --org ${orgId}`);
|
|
48
76
|
info(`DNS propagation can take a few minutes — track it with: myapi domain status ${domainArg}`);
|
|
49
77
|
info(`Your funnel will be live on https://${domainArg} once propagation completes.`);
|
|
50
78
|
}
|
|
79
|
+
// Renew a registered domain for one more registration period (typically 1 year).
|
|
80
|
+
// Charged against the org's billing balance — surface a confirmation unless --yes.
|
|
81
|
+
export async function renew(domainArg, flags) {
|
|
82
|
+
const config = requireConfig();
|
|
83
|
+
const orgId = requireOrg(flags, config, 'myapi domain renew <domain> [--yes] [--org <id>]');
|
|
84
|
+
if (!domainArg)
|
|
85
|
+
error('Missing required arguments.\nUsage: myapi domain renew <domain> [--yes] [--org <id>]');
|
|
86
|
+
await sdkDomain.renewDomain(config.api_key, orgId, domainArg);
|
|
87
|
+
success(`Renewed ${domainArg}.`);
|
|
88
|
+
info(`Track expiry with: myapi domain status ${domainArg}`);
|
|
89
|
+
}
|
|
51
90
|
export async function list(flags) {
|
|
52
91
|
const config = requireConfig();
|
|
53
92
|
const orgId = requireOrg(flags, config, 'myapi domain list [--filter=all|unassigned|org] [--org <id>]');
|
|
@@ -137,15 +176,40 @@ Note: --filter=all and --filter=unassigned are account-wide; --org is only used
|
|
|
137
176
|
to authenticate the request.`,
|
|
138
177
|
'check': 'myapi domain check <domain> [--org <id>] [--json]',
|
|
139
178
|
'register': `myapi domain register <domain> [--years <num>] [--org <id>]
|
|
179
|
+
<registrant flags — see below>
|
|
140
180
|
|
|
141
181
|
--years <num> Number of years to register for (default: 1)
|
|
142
182
|
|
|
143
183
|
Prerequisites:
|
|
144
184
|
- Sufficient balance (myapi billing balance)
|
|
145
185
|
- Domain available (myapi domain check <domain>)
|
|
186
|
+
- WHOIS registrant info (ICANN requirement — see below)
|
|
187
|
+
|
|
188
|
+
Registrant (required at every register call):
|
|
189
|
+
Cheapest path: run \`myapi auth registrant set\` once. The CLI stores
|
|
190
|
+
your name/email/phone/address and auto-injects it on every register.
|
|
191
|
+
|
|
192
|
+
Per-call override (agent-friendly):
|
|
193
|
+
--registrant-json '{"name":"...", "email":"...", "phone":"+33...", "street":"...",
|
|
194
|
+
"city":"...", "postal_code":"...", "country_code":"DE"}'
|
|
195
|
+
or per-field:
|
|
196
|
+
--registrant-name --registrant-email --registrant-phone --registrant-street
|
|
197
|
+
--registrant-city --registrant-postal-code --registrant-country
|
|
198
|
+
--registrant-state (required for US/CA only)
|
|
199
|
+
--registrant-organization (optional)
|
|
200
|
+
|
|
201
|
+
If no source provides it and the terminal is interactive, you'll be
|
|
202
|
+
prompted for the missing fields and offered to save them.
|
|
146
203
|
|
|
147
204
|
After registering: myapi domain assign <domain>
|
|
148
205
|
DNS propagation takes a few minutes — track it with: myapi domain status <domain>`,
|
|
206
|
+
'renew': `myapi domain renew <domain> [--org <id>]
|
|
207
|
+
|
|
208
|
+
Renews a registered domain for one more registration period (typically 1 year).
|
|
209
|
+
Charged against the org's billing balance — confirm with "myapi billing balance"
|
|
210
|
+
before running.
|
|
211
|
+
|
|
212
|
+
Track expiry afterward with: myapi domain status <domain>`,
|
|
149
213
|
'assign': `myapi domain assign <domain> [--org <id>]
|
|
150
214
|
|
|
151
215
|
Assigns a registered domain to an org. Once assigned, your funnel is served at
|
|
@@ -175,6 +239,7 @@ Subcommands:
|
|
|
175
239
|
list List domains
|
|
176
240
|
check Check domain availability
|
|
177
241
|
register Register a domain
|
|
242
|
+
renew Renew a registered domain for another period
|
|
178
243
|
assign Assign domain to an org
|
|
179
244
|
unassign Unassign domain from its current org
|
|
180
245
|
status Get domain status
|
|
@@ -196,6 +261,7 @@ Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to sk
|
|
|
196
261
|
case 'list': return list(flags);
|
|
197
262
|
case 'check': return check(args[0], flags);
|
|
198
263
|
case 'register': return register(args[0], flags);
|
|
264
|
+
case 'renew': return renew(args[0], flags);
|
|
199
265
|
case 'assign': return assign(args[0], flags);
|
|
200
266
|
case 'unassign': return unassign(args[0], flags);
|
|
201
267
|
case 'status': return status(args[0], flags);
|
|
@@ -2,6 +2,19 @@ import { email as sdkEmail } from '@myapihq/sdk';
|
|
|
2
2
|
import { requireConfig } from '../../config.js';
|
|
3
3
|
import { success, error, printTable, printJson, info } from '../../output.js';
|
|
4
4
|
import { requireOrg } from '../../helpers.js';
|
|
5
|
+
export const EXPOSES = [
|
|
6
|
+
'POST /email/orgs/{org_id}/campaigns',
|
|
7
|
+
'GET /email/orgs/{org_id}/campaigns',
|
|
8
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
9
|
+
'PATCH /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
10
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-list',
|
|
11
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-file',
|
|
12
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/status',
|
|
13
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/start',
|
|
14
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/pause',
|
|
15
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resume',
|
|
16
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/stats',
|
|
17
|
+
];
|
|
5
18
|
async function create(nameArg, flags) {
|
|
6
19
|
const config = requireConfig();
|
|
7
20
|
const orgId = requireOrg(flags, config, 'myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]');
|
|
@@ -61,6 +74,23 @@ async function uploadContacts(id, flags) {
|
|
|
61
74
|
const res = await sdkEmail.uploadContactList(config.api_key, orgId, id, emails);
|
|
62
75
|
success(`Uploaded ${res.total} contacts (status: ${res.status})`);
|
|
63
76
|
}
|
|
77
|
+
async function uploadContactsFile(id, flags) {
|
|
78
|
+
const config = requireConfig();
|
|
79
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]');
|
|
80
|
+
if (!id || !flags.file) {
|
|
81
|
+
error('Missing required arguments.\nUsage: myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]');
|
|
82
|
+
}
|
|
83
|
+
// Defer fs require to keep the unimported-cost low for users who never
|
|
84
|
+
// touch this command. Plain JS readFile fits — the SDK accepts a Buffer.
|
|
85
|
+
const fs = await import('node:fs');
|
|
86
|
+
const path = await import('node:path');
|
|
87
|
+
const filePath = flags.file;
|
|
88
|
+
if (!fs.existsSync(filePath))
|
|
89
|
+
error(`File not found: ${filePath}`);
|
|
90
|
+
const buf = fs.readFileSync(filePath);
|
|
91
|
+
const res = await sdkEmail.uploadContactsFile(config.api_key, orgId, id, buf, path.basename(filePath));
|
|
92
|
+
success(`Uploaded ${res.total} contacts from ${filePath} (status: ${res.status})`);
|
|
93
|
+
}
|
|
64
94
|
async function start(id, flags) {
|
|
65
95
|
const config = requireConfig();
|
|
66
96
|
const orgId = requireOrg(flags, config, 'myapi email campaign start <id> [--org <id>]');
|
|
@@ -106,6 +136,7 @@ Either form works; the positional name is the recommended shape.`,
|
|
|
106
136
|
'get': 'myapi email campaign get <id> [--org <id>]',
|
|
107
137
|
'update': 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]',
|
|
108
138
|
'upload-contacts': 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]',
|
|
139
|
+
'upload-contacts-file': 'myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]',
|
|
109
140
|
'start': 'myapi email campaign start <id> [--org <id>]',
|
|
110
141
|
'pause': 'myapi email campaign pause <id> [--org <id>]',
|
|
111
142
|
'resume': 'myapi email campaign resume <id> [--org <id>]',
|
|
@@ -120,7 +151,8 @@ Subcommands:
|
|
|
120
151
|
list List campaigns
|
|
121
152
|
get <id> Get a single campaign
|
|
122
153
|
update <id> Patch name / per-day-limit
|
|
123
|
-
upload-contacts <id>
|
|
154
|
+
upload-contacts <id> Upload contact list (--emails a@x,b@y)
|
|
155
|
+
upload-contacts-file <id> Upload contacts from a CSV file (--file path/to/contacts.csv)
|
|
124
156
|
start <id> Start a campaign
|
|
125
157
|
pause <id> Pause a campaign
|
|
126
158
|
resume <id> Resume a campaign
|
|
@@ -143,6 +175,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
|
143
175
|
case 'get': return get(args[0], flags);
|
|
144
176
|
case 'update': return update(args[0], flags);
|
|
145
177
|
case 'upload-contacts': return uploadContacts(args[0], flags);
|
|
178
|
+
case 'upload-contacts-file': return uploadContactsFile(args[0], flags);
|
|
146
179
|
case 'start': return start(args[0], flags);
|
|
147
180
|
case 'pause': return pause(args[0], flags);
|
|
148
181
|
case 'resume': return resume(args[0], flags);
|
|
@@ -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>;
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
// namespace lives in its own file in this directory; this index just
|
|
3
3
|
// routes to the right one and handles the deprecated flat aliases.
|
|
4
4
|
import { error, info } from '../../output.js';
|
|
5
|
+
// The email index is a dispatcher only; each subcommand file declares
|
|
6
|
+
// its own EXPOSES. Empty here so the coverage tool can still introspect
|
|
7
|
+
// every file uniformly.
|
|
8
|
+
export const EXPOSES = [];
|
|
5
9
|
import * as mailbox from './mailbox.js';
|
|
6
10
|
import * as message from './message.js';
|
|
7
11
|
import * as warmup from './warmup.js';
|
|
8
12
|
import * as template from './template.js';
|
|
9
13
|
import * as campaign from './campaign.js';
|
|
14
|
+
import * as verify from './verify.js';
|
|
10
15
|
export const SCHEMA = {
|
|
11
16
|
org: 'string',
|
|
12
17
|
domain: 'string',
|
|
@@ -27,6 +32,7 @@ export const SCHEMA = {
|
|
|
27
32
|
name: 'string',
|
|
28
33
|
emails: 'string',
|
|
29
34
|
'per-day': 'number',
|
|
35
|
+
file: 'string',
|
|
30
36
|
};
|
|
31
37
|
// Old flat subcommand → [namespace, sub]. Kept so existing scripts/skills
|
|
32
38
|
// don't break. Emits a one-line stderr deprecation note then runs the new
|
|
@@ -63,6 +69,7 @@ async function dispatchNamespace(ns, sub, restArgs, flags) {
|
|
|
63
69
|
case 'warmup': return warmup.run(sub, restArgs, flags);
|
|
64
70
|
case 'template': return template.run(sub, restArgs, flags);
|
|
65
71
|
case 'campaign': return campaign.run(sub, restArgs, flags);
|
|
72
|
+
case 'verify': return verify.run(sub, restArgs, flags);
|
|
66
73
|
default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -76,12 +83,14 @@ Namespaces:
|
|
|
76
83
|
warmup Warmup controls (account-scoped) — start, pause, resume, stop, stats
|
|
77
84
|
template Email templates (org-scoped) — generate, list, edit, send-test, delete
|
|
78
85
|
campaign Email campaigns (org-scoped) — create, list, get, update, upload-contacts, start, pause, resume, stats
|
|
86
|
+
verify Sync single-address email verification (org-scoped) — syntax + DNS + Microsoft probe
|
|
79
87
|
|
|
80
88
|
Examples:
|
|
81
89
|
myapi email mailbox create hello@example.com
|
|
82
90
|
myapi email message send --from a@x --to b@y --subject hi --body "hello"
|
|
83
91
|
myapi email template generate welcome --prompt "Welcome email"
|
|
84
92
|
myapi email campaign create "Q2 Launch" --template-id <id> --from a@x
|
|
93
|
+
myapi email verify alice@example.com
|
|
85
94
|
|
|
86
95
|
Org-scoped commands accept --org <id> (or set default: myapi config set-org <id>).
|
|
87
96
|
Run "myapi email <namespace> --help" for namespace-specific commands.`);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { email as sdkEmail } from '@myapihq/sdk';
|
|
2
2
|
import { requireConfig } from '../../config.js';
|
|
3
3
|
import { success, error, printTable, info } from '../../output.js';
|
|
4
|
+
export const EXPOSES = [
|
|
5
|
+
'POST /email/mailboxes/create',
|
|
6
|
+
'GET /email/mailboxes',
|
|
7
|
+
'POST /email/sending/activate',
|
|
8
|
+
];
|
|
4
9
|
async function create(addressArg, flags) {
|
|
5
10
|
const config = requireConfig();
|
|
6
11
|
// Convention: first required arg is positional. For mailboxes that's the
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { email as sdkEmail } from '@myapihq/sdk';
|
|
2
2
|
import { requireConfig } from '../../config.js';
|
|
3
3
|
import { success, error, printTable, printJson, info } from '../../output.js';
|
|
4
|
+
export const EXPOSES = [
|
|
5
|
+
'POST /email/send',
|
|
6
|
+
'GET /email/status/{message_id}',
|
|
7
|
+
'GET /email/sent',
|
|
8
|
+
'GET /email/inbox/{address}',
|
|
9
|
+
'GET /email/outbox/{address}',
|
|
10
|
+
'GET /email/message/{message_id}',
|
|
11
|
+
];
|
|
4
12
|
async function send(flags) {
|
|
5
13
|
const config = requireConfig();
|
|
6
14
|
if (!flags.from || !flags.to || !flags.subject) {
|