@myapihq/cli 1.2.8 → 1.2.9

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.
@@ -31,6 +31,7 @@ export const SCHEMA = {
31
31
  prompt: 'string',
32
32
  name: 'string',
33
33
  emails: 'string',
34
+ quick: 'boolean',
34
35
  'per-day': 'number',
35
36
  file: 'string',
36
37
  };
@@ -3,5 +3,5 @@ import { type Flags } from '../../helpers.js';
3
3
  import type { Exposes } from '../../exposes.js';
4
4
  export declare const EXPOSES: Exposes;
5
5
  export declare const SCHEMA: FlagSchema;
6
- export declare const VERIFY_HELP = "Usage: myapi email verify <email> [--org <id>] [--json]\n\nSync single-address email verification. Cheap layer only: syntax + DNS +\nMicrosoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;\nthe rest get verdict='unknown' with smtp_recommended=true.\n\nVerdicts:\n deliverable high-confidence \u2014 the address accepts mail\n undeliverable high-confidence \u2014 syntax bad, DNS missing, or Microsoft rejects\n unknown not enough signal; consider an SMTP probe (not in this API)\n\nUse --json for the full check breakdown (syntax / DNS / Microsoft probes).\n";
7
- export declare function run(emailArg: string | undefined, _rest: string[], flags?: Flags): Promise<void>;
6
+ export declare const VERIFY_HELP = "Usage: myapi email verify <email> [--org <id>] [--json]\n myapi email verify bulk [--quick] [--org <id>] < emails.txt\n myapi email verify job <job_id> [--org <id>] [--json]\n\nSingle-address verification (sync): syntax + DNS + Microsoft probe.\nReturns a verdict in <1s for ~50% of inputs; the rest get\nverdict='unknown' with smtp_recommended=true.\n\nBulk verification (async): reads up to 500 addresses from stdin (one per\nline), starts a job, and prints a job_id. It runs the cheap verdict on\nevery address then SMTP-probes the uncertain ones. --quick skips the\ncatch-all check (faster, but catch-all domains aren't flagged). Poll with\n\"myapi email verify job <job_id>\".\n";
7
+ export declare function run(arg: string | undefined, rest: string[], flags?: Flags): Promise<void>;
@@ -1,31 +1,85 @@
1
1
  import { email as sdkEmail } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../../config.js';
3
- import { error, info, printJson } from '../../output.js';
3
+ import { success, error, info, printTable, printJson } from '../../output.js';
4
4
  import { requireOrg } from '../../helpers.js';
5
5
  export const EXPOSES = [
6
6
  'POST /email/orgs/{org_id}/verify',
7
+ 'POST /email/orgs/{org_id}/verify-bulk',
8
+ 'GET /email/orgs/{org_id}/verify-jobs/{id}',
7
9
  ];
8
10
  export const SCHEMA = {};
9
11
  export const VERIFY_HELP = `Usage: myapi email verify <email> [--org <id>] [--json]
12
+ myapi email verify bulk [--quick] [--org <id>] < emails.txt
13
+ myapi email verify job <job_id> [--org <id>] [--json]
10
14
 
11
- Sync single-address email verification. Cheap layer only: syntax + DNS +
12
- Microsoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;
13
- the rest get verdict='unknown' with smtp_recommended=true.
15
+ Single-address verification (sync): syntax + DNS + Microsoft probe.
16
+ Returns a verdict in <1s for ~50% of inputs; the rest get
17
+ verdict='unknown' with smtp_recommended=true.
14
18
 
15
- Verdicts:
16
- deliverable high-confidence the address accepts mail
17
- undeliverable high-confidence syntax bad, DNS missing, or Microsoft rejects
18
- unknown not enough signal; consider an SMTP probe (not in this API)
19
-
20
- Use --json for the full check breakdown (syntax / DNS / Microsoft probes).
19
+ Bulk verification (async): reads up to 500 addresses from stdin (one per
20
+ line), starts a job, and prints a job_id. It runs the cheap verdict on
21
+ every address then SMTP-probes the uncertain ones. --quick skips the
22
+ catch-all check (faster, but catch-all domains aren't flagged). Poll with
23
+ "myapi email verify job <job_id>".
21
24
  `;
22
- export async function run(emailArg, _rest, flags = {}) {
23
- if (flags.help) {
24
- info(VERIFY_HELP);
25
+ async function readStdinLines() {
26
+ if (process.stdin.isTTY) {
27
+ error('No input on stdin. Pipe a newline-separated list of addresses:\n myapi email verify bulk < emails.txt');
28
+ }
29
+ const data = await new Promise((resolve, reject) => {
30
+ let buf = '';
31
+ process.stdin.setEncoding('utf-8');
32
+ process.stdin.on('data', c => { buf += c; });
33
+ process.stdin.on('end', () => resolve(buf));
34
+ process.stdin.on('error', reject);
35
+ });
36
+ return data.split('\n').map(l => l.trim()).filter(Boolean);
37
+ }
38
+ async function bulk(flags) {
39
+ const config = requireConfig();
40
+ const orgId = requireOrg(flags, config, 'myapi email verify bulk [--quick] [--org <id>] < emails.txt');
41
+ const emails = await readStdinLines();
42
+ if (emails.length === 0)
43
+ error('No addresses on stdin. Provide a newline-separated list.');
44
+ if (emails.length > 500)
45
+ error(`Too many addresses (${emails.length}). The bulk limit is 500 per job.`);
46
+ // --quick turns off the catch-all check (catchAll=false).
47
+ const job = await sdkEmail.verifyBulk(config.api_key, orgId, emails, flags.quick ? false : undefined);
48
+ if (flags.json) {
49
+ printJson(job);
50
+ return;
51
+ }
52
+ success(`Bulk verification started — ${job.total} address(es)`);
53
+ info(`Job: ${job.job_id} · status: ${job.status}`);
54
+ info(`Poll it with: myapi email verify job ${job.job_id}`);
55
+ }
56
+ async function job(jobId, flags) {
57
+ if (!jobId)
58
+ error('Missing job id.\nUsage: myapi email verify job <job_id>');
59
+ const config = requireConfig();
60
+ const orgId = requireOrg(flags, config, 'myapi email verify job <job_id> [--org <id>]');
61
+ const res = await sdkEmail.getVerifyJob(config.api_key, orgId, jobId);
62
+ if (flags.json) {
63
+ printJson(res);
25
64
  return;
26
65
  }
27
- if (!emailArg)
28
- error('Missing required argument <email>.\nUsage: myapi email verify <email> [--org <id>] [--json]');
66
+ info(`Job: ${res.job_id}`);
67
+ info(`Status: ${res.status}`);
68
+ info(`Progress: ${res.completed ?? 0} / ${res.total}`);
69
+ if (res.error)
70
+ info(`Error: ${res.error}`);
71
+ if (res.results && res.results.length > 0) {
72
+ info('');
73
+ printTable(res.results.map(r => ({
74
+ email: r.email,
75
+ verdict: r.verdict,
76
+ confidence: r.confidence.toFixed(2),
77
+ source: r.source,
78
+ detail: r.detail || '',
79
+ })), { flags });
80
+ }
81
+ }
82
+ async function single(emailArg, flags) {
29
83
  const config = requireConfig();
30
84
  const orgId = requireOrg(flags, config, 'myapi email verify <email> [--org <id>]');
31
85
  const res = await sdkEmail.verifyEmail(config.api_key, orgId, emailArg);
@@ -38,7 +92,7 @@ export async function run(emailArg, _rest, flags = {}) {
38
92
  : '? unknown';
39
93
  info(`Email: ${res.email}`);
40
94
  info(`Verdict: ${verdictLabel} (confidence ${res.confidence.toFixed(2)})`);
41
- info(`SMTP next: ${res.smtp_recommended ? 'yes — consider an SMTP probe' : 'no — verdict is definitive'}`);
95
+ info(`SMTP next: ${res.smtp_recommended ? 'yes — consider a bulk job (SMTP-probes the uncertain ones)' : 'no — verdict is definitive'}`);
42
96
  info(`Took: ${res.elapsed_ms}ms`);
43
97
  if (res.checks.syntax.detail)
44
98
  info(`Syntax: ${res.checks.syntax.detail}`);
@@ -50,3 +104,18 @@ export async function run(emailArg, _rest, flags = {}) {
50
104
  if (res.checks.microsoft)
51
105
  info(`Microsoft: ${res.checks.microsoft.verdict}`);
52
106
  }
107
+ // `arg` is the first token after `verify`: the literal `bulk` / `job`
108
+ // selects the async modes; anything else is treated as a single address.
109
+ export async function run(arg, rest, flags = {}) {
110
+ if (flags.help) {
111
+ info(VERIFY_HELP);
112
+ return;
113
+ }
114
+ if (arg === 'bulk')
115
+ return bulk(flags);
116
+ if (arg === 'job')
117
+ return job(rest[0], flags);
118
+ if (!arg)
119
+ error('Missing required argument <email>.\nUsage: myapi email verify <email> (or: verify bulk | verify job <id>)');
120
+ return single(arg, flags);
121
+ }
@@ -0,0 +1,22 @@
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 create(name: string, flags: Flags): Promise<void>;
7
+ export declare function list(flags: Flags): Promise<void>;
8
+ export declare function get(repo: string, flags: Flags): Promise<void>;
9
+ export declare function del(repo: string, flags: Flags): Promise<void>;
10
+ export declare function refs(repo: string, flags: Flags): Promise<void>;
11
+ export declare function log(repo: string, flags: Flags): Promise<void>;
12
+ export declare function show(repo: string, sha: string, flags: Flags): Promise<void>;
13
+ export declare function tree(repo: string, ref: string, flags: Flags): Promise<void>;
14
+ export declare function blob(repo: string, ref: string, path: string, flags: Flags): Promise<void>;
15
+ export declare function diff(repo: string, flags: Flags): Promise<void>;
16
+ export declare function commit(repo: string, flags: Flags): Promise<void>;
17
+ export declare function createBranch(repo: string, name: string, flags: Flags): Promise<void>;
18
+ export declare function deleteBranch(repo: string, branch: string, flags: Flags): Promise<void>;
19
+ export declare function tag(repo: string, name: string, flags: Flags): Promise<void>;
20
+ export declare function merge(repo: string, flags: Flags): Promise<void>;
21
+ export declare function repack(repo: string, flags: Flags): Promise<void>;
22
+ export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -0,0 +1,367 @@
1
+ import { git as sdkGit } from '@myapihq/sdk';
2
+ import { requireConfig } from '../config.js';
3
+ import { success, error, printTable, info, printJson } from '../output.js';
4
+ import { formatDate } from '../utils.js';
5
+ import { requireOrg, requireArg } from '../helpers.js';
6
+ export const EXPOSES = [
7
+ 'POST /git/orgs/{org_id}/repos',
8
+ 'GET /git/orgs/{org_id}/repos',
9
+ 'GET /git/orgs/{org_id}/repos/{repo}',
10
+ 'DELETE /git/orgs/{org_id}/repos/{repo}',
11
+ 'GET /git/orgs/{org_id}/repos/{repo}/refs',
12
+ 'GET /git/orgs/{org_id}/repos/{repo}/tree/{ref}',
13
+ 'GET /git/orgs/{org_id}/repos/{repo}/blob/{ref}/{path}',
14
+ 'GET /git/orgs/{org_id}/repos/{repo}/commits',
15
+ 'GET /git/orgs/{org_id}/repos/{repo}/commits/{sha}',
16
+ 'GET /git/orgs/{org_id}/repos/{repo}/diff',
17
+ 'POST /git/orgs/{org_id}/repos/{repo}/commits',
18
+ 'POST /git/orgs/{org_id}/repos/{repo}/branches',
19
+ 'DELETE /git/orgs/{org_id}/repos/{repo}/branches/{branch}',
20
+ 'POST /git/orgs/{org_id}/repos/{repo}/tags',
21
+ 'POST /git/orgs/{org_id}/repos/{repo}/merges',
22
+ 'POST /git/orgs/{org_id}/repos/{repo}/repack',
23
+ ];
24
+ export const SCHEMA = {
25
+ name: 'string',
26
+ 'default-branch': 'string',
27
+ ref: 'string',
28
+ limit: 'number',
29
+ path: 'string',
30
+ base: 'string',
31
+ head: 'string',
32
+ branch: 'string',
33
+ message: 'string',
34
+ from: 'string',
35
+ 'author-name': 'string',
36
+ 'author-email': 'string',
37
+ changes: 'string',
38
+ target: 'string',
39
+ source: 'string',
40
+ };
41
+ const shortSha = (s) => (s || '').slice(0, 8);
42
+ const firstLine = (s) => (s || '').split('\n')[0];
43
+ // ── Repos ────────────────────────────────────────────────────────────────────
44
+ export async function create(name, flags) {
45
+ const config = requireConfig();
46
+ const orgId = requireOrg(flags, config, 'myapi git create <name> [--default-branch <b>] [--org <id>]');
47
+ const repoName = name || flags.name;
48
+ requireArg(repoName, 'name', 'myapi git create <name> [--default-branch <b>]');
49
+ const res = await sdkGit.createRepo(config.api_key, orgId, repoName, flags['default-branch']);
50
+ success(`Repository created: ${res.name}`);
51
+ info(`Default branch: ${res.default_branch}`);
52
+ }
53
+ export async function list(flags) {
54
+ const config = requireConfig();
55
+ const orgId = requireOrg(flags, config, 'myapi git list [--org <id>]');
56
+ const repos = await sdkGit.listRepos(config.api_key, orgId);
57
+ if (flags.json) {
58
+ printJson(repos);
59
+ return;
60
+ }
61
+ printTable(repos.map(r => ({ name: r.name })), {
62
+ flags,
63
+ empty: 'No repositories yet. Create one with: myapi git create <name>',
64
+ });
65
+ }
66
+ export async function get(repo, flags) {
67
+ const config = requireConfig();
68
+ const orgId = requireOrg(flags, config, 'myapi git get <repo> [--org <id>]');
69
+ requireArg(repo, 'repo', 'myapi git get <repo>');
70
+ const r = await sdkGit.getRepo(config.api_key, orgId, repo);
71
+ if (flags.json) {
72
+ printJson(r);
73
+ return;
74
+ }
75
+ info(`Name: ${r.name}`);
76
+ info(`Default branch: ${r.default_branch}`);
77
+ info(`Branches: ${r.branches}`);
78
+ info(`Tags: ${r.tags}`);
79
+ }
80
+ export async function del(repo, flags) {
81
+ const config = requireConfig();
82
+ const orgId = requireOrg(flags, config, 'myapi git delete <repo> [--org <id>]');
83
+ requireArg(repo, 'repo', 'myapi git delete <repo>');
84
+ await sdkGit.deleteRepo(config.api_key, orgId, repo);
85
+ success(`Deleted repository ${repo}`);
86
+ }
87
+ // ── Refs / history ───────────────────────────────────────────────────────────
88
+ export async function refs(repo, flags) {
89
+ const config = requireConfig();
90
+ const orgId = requireOrg(flags, config, 'myapi git refs <repo> [--org <id>]');
91
+ requireArg(repo, 'repo', 'myapi git refs <repo>');
92
+ const r = await sdkGit.listRefs(config.api_key, orgId, repo);
93
+ if (flags.json) {
94
+ printJson(r);
95
+ return;
96
+ }
97
+ info(`HEAD: ${r.head}`);
98
+ info(`Branches (${r.branches.length}):`);
99
+ for (const b of r.branches)
100
+ info(` ${b.name} ${shortSha(b.sha)}`);
101
+ info(`Tags (${r.tags.length}):`);
102
+ for (const t of r.tags)
103
+ info(` ${t.name} ${shortSha(t.sha)}`);
104
+ }
105
+ export async function log(repo, flags) {
106
+ const config = requireConfig();
107
+ const orgId = requireOrg(flags, config, 'myapi git log <repo> [--ref <r>] [--limit <n>] [--org <id>]');
108
+ requireArg(repo, 'repo', 'myapi git log <repo>');
109
+ const commits = await sdkGit.listCommits(config.api_key, orgId, repo, {
110
+ ref: flags.ref,
111
+ limit: typeof flags.limit === 'number' ? flags.limit : undefined,
112
+ });
113
+ if (flags.json) {
114
+ printJson(commits);
115
+ return;
116
+ }
117
+ printTable(commits.map(c => ({
118
+ sha: shortSha(c.sha),
119
+ message: firstLine(c.message),
120
+ author: c.author,
121
+ when: c.when ? formatDate(c.when) : '',
122
+ })), { flags, empty: 'No commits.' });
123
+ }
124
+ export async function show(repo, sha, flags) {
125
+ const config = requireConfig();
126
+ const orgId = requireOrg(flags, config, 'myapi git show <repo> <sha> [--org <id>]');
127
+ requireArg(repo, 'repo', 'myapi git show <repo> <sha>');
128
+ requireArg(sha, 'sha', 'myapi git show <repo> <sha>');
129
+ const c = await sdkGit.getCommit(config.api_key, orgId, repo, sha);
130
+ if (flags.json) {
131
+ printJson(c);
132
+ return;
133
+ }
134
+ info(`Commit: ${c.sha}`);
135
+ info(`Author: ${c.author} <${c.email}>`);
136
+ info(`Date: ${c.when ? formatDate(c.when) : ''}`);
137
+ if (c.parents?.length)
138
+ info(`Parents: ${c.parents.map(shortSha).join(' ')}`);
139
+ info('');
140
+ info(c.message);
141
+ }
142
+ export async function tree(repo, ref, flags) {
143
+ const config = requireConfig();
144
+ const orgId = requireOrg(flags, config, 'myapi git tree <repo> <ref> [--path <p>] [--org <id>]');
145
+ requireArg(repo, 'repo', 'myapi git tree <repo> <ref>');
146
+ requireArg(ref, 'ref', 'myapi git tree <repo> <ref>');
147
+ const entries = await sdkGit.listTree(config.api_key, orgId, repo, ref, flags.path);
148
+ if (flags.json) {
149
+ printJson(entries);
150
+ return;
151
+ }
152
+ printTable(entries.map(e => ({
153
+ type: e.type,
154
+ mode: e.mode,
155
+ size: e.type === 'file' ? e.size : '',
156
+ path: e.path,
157
+ })), { flags, empty: 'Empty tree.' });
158
+ }
159
+ export async function blob(repo, ref, path, flags) {
160
+ const config = requireConfig();
161
+ const orgId = requireOrg(flags, config, 'myapi git blob <repo> <ref> <path> [--org <id>]');
162
+ requireArg(repo, 'repo', 'myapi git blob <repo> <ref> <path>');
163
+ requireArg(ref, 'ref', 'myapi git blob <repo> <ref> <path>');
164
+ requireArg(path, 'path', 'myapi git blob <repo> <ref> <path>');
165
+ const b = await sdkGit.readBlob(config.api_key, orgId, repo, ref, path);
166
+ if (flags.json) {
167
+ printJson(b);
168
+ return;
169
+ }
170
+ // Decode and print the file content raw — `git blob` is a `cat`.
171
+ process.stdout.write(Buffer.from(b.content_base64, 'base64').toString('utf-8'));
172
+ }
173
+ export async function diff(repo, flags) {
174
+ const config = requireConfig();
175
+ const orgId = requireOrg(flags, config, 'myapi git diff <repo> --base <ref> --head <ref> [--org <id>]');
176
+ requireArg(repo, 'repo', 'myapi git diff <repo> --base <ref> --head <ref>');
177
+ const base = flags.base;
178
+ const head = flags.head;
179
+ if (!base || !head)
180
+ error('Both --base <ref> and --head <ref> are required.\nUsage: myapi git diff <repo> --base <ref> --head <ref>');
181
+ const text = await sdkGit.getDiff(config.api_key, orgId, repo, base, head);
182
+ if (flags.json) {
183
+ printJson({ diff: text });
184
+ return;
185
+ }
186
+ process.stdout.write(text.endsWith('\n') || text === '' ? text : text + '\n');
187
+ }
188
+ // ── Writes ───────────────────────────────────────────────────────────────────
189
+ export async function commit(repo, flags) {
190
+ const config = requireConfig();
191
+ const orgId = requireOrg(flags, config, 'myapi git commit <repo> --branch <b> --message <m> --changes <json> [--org <id>]');
192
+ requireArg(repo, 'repo', 'myapi git commit <repo> --branch <b> --message <m> --changes <json>');
193
+ const branch = flags.branch;
194
+ if (!branch)
195
+ error('Missing --branch.\nUsage: myapi git commit <repo> --branch <b> --message <m> --changes <json>');
196
+ if (typeof flags.changes !== 'string') {
197
+ error('Missing --changes.\n--changes is a JSON array of file edits, e.g.\n --changes \'[{"path":"README.md","content":"# Hello"}]\'\nEach entry: {path, content | content_base64 | delete:true, mode?}.');
198
+ }
199
+ let changes;
200
+ try {
201
+ changes = JSON.parse(flags.changes);
202
+ if (!Array.isArray(changes))
203
+ throw new Error('not an array');
204
+ }
205
+ catch (e) {
206
+ error(`--changes is not valid JSON: ${e?.message ?? e}`);
207
+ }
208
+ const payload = {
209
+ branch,
210
+ message: flags.message || '',
211
+ changes: changes,
212
+ };
213
+ if (typeof flags.base === 'string')
214
+ payload.base = flags.base;
215
+ if (flags['author-name'] || flags['author-email']) {
216
+ payload.author = {
217
+ name: flags['author-name'] || '',
218
+ email: flags['author-email'] || '',
219
+ };
220
+ }
221
+ const res = await sdkGit.commit(config.api_key, orgId, repo, payload);
222
+ if (flags.json) {
223
+ printJson(res);
224
+ return;
225
+ }
226
+ success(`Committed ${shortSha(res.sha)} to ${res.branch}`);
227
+ info(`Tree: ${shortSha(res.tree)}`);
228
+ }
229
+ export async function createBranch(repo, name, flags) {
230
+ const config = requireConfig();
231
+ const orgId = requireOrg(flags, config, 'myapi git create-branch <repo> <name> --from <ref> [--org <id>]');
232
+ requireArg(repo, 'repo', 'myapi git create-branch <repo> <name> --from <ref>');
233
+ requireArg(name, 'name', 'myapi git create-branch <repo> <name> --from <ref>');
234
+ const from = flags.from;
235
+ if (!from)
236
+ error('Missing --from <ref>.\nUsage: myapi git create-branch <repo> <name> --from <ref>');
237
+ await sdkGit.createBranch(config.api_key, orgId, repo, name, from);
238
+ success(`Created branch ${name} (from ${from})`);
239
+ }
240
+ export async function deleteBranch(repo, branch, flags) {
241
+ const config = requireConfig();
242
+ const orgId = requireOrg(flags, config, 'myapi git delete-branch <repo> <branch> [--org <id>]');
243
+ requireArg(repo, 'repo', 'myapi git delete-branch <repo> <branch>');
244
+ requireArg(branch, 'branch', 'myapi git delete-branch <repo> <branch>');
245
+ await sdkGit.deleteBranch(config.api_key, orgId, repo, branch);
246
+ success(`Deleted branch ${branch}`);
247
+ }
248
+ export async function tag(repo, name, flags) {
249
+ const config = requireConfig();
250
+ const orgId = requireOrg(flags, config, 'myapi git tag <repo> <name> --ref <ref> [--org <id>]');
251
+ requireArg(repo, 'repo', 'myapi git tag <repo> <name> --ref <ref>');
252
+ requireArg(name, 'name', 'myapi git tag <repo> <name> --ref <ref>');
253
+ const ref = flags.ref;
254
+ if (!ref)
255
+ error('Missing --ref <ref>.\nUsage: myapi git tag <repo> <name> --ref <ref>');
256
+ await sdkGit.createTag(config.api_key, orgId, repo, name, ref);
257
+ success(`Created tag ${name} → ${ref}`);
258
+ }
259
+ export async function merge(repo, flags) {
260
+ const config = requireConfig();
261
+ const orgId = requireOrg(flags, config, 'myapi git merge <repo> --target <b> --source <b> [--org <id>]');
262
+ requireArg(repo, 'repo', 'myapi git merge <repo> --target <b> --source <b>');
263
+ const target = flags.target;
264
+ const source = flags.source;
265
+ if (!target || !source)
266
+ error('Both --target <branch> and --source <branch> are required.\nUsage: myapi git merge <repo> --target <b> --source <b>\n(Merge is fast-forward only.)');
267
+ const res = await sdkGit.merge(config.api_key, orgId, repo, target, source);
268
+ success(`Merged ${source} into ${target} → ${shortSha(res.sha)}`);
269
+ }
270
+ export async function repack(repo, flags) {
271
+ const config = requireConfig();
272
+ const orgId = requireOrg(flags, config, 'myapi git repack <repo> [--org <id>]');
273
+ requireArg(repo, 'repo', 'myapi git repack <repo>');
274
+ const res = await sdkGit.repack(config.api_key, orgId, repo);
275
+ if (flags.json) {
276
+ printJson(res);
277
+ return;
278
+ }
279
+ success(`Repacked ${repo}`);
280
+ info(`Packs: ${res.packs_before} → ${res.packs_after} · ${res.objects} objects`);
281
+ }
282
+ // ── Dispatcher ───────────────────────────────────────────────────────────────
283
+ const SUBCOMMAND_USAGE = {
284
+ 'create': 'myapi git create <name> [--default-branch <b>] [--org <id>]',
285
+ 'list': 'myapi git list [--org <id>] [--json]',
286
+ 'get': 'myapi git get <repo> [--org <id>] [--json]',
287
+ 'delete': 'myapi git delete <repo> [--org <id>]',
288
+ 'refs': 'myapi git refs <repo> [--org <id>] [--json]',
289
+ 'log': 'myapi git log <repo> [--ref <r>] [--limit <n>] [--org <id>] [--json]',
290
+ 'show': 'myapi git show <repo> <sha> [--org <id>] [--json]',
291
+ 'tree': 'myapi git tree <repo> <ref> [--path <p>] [--org <id>] [--json]',
292
+ 'blob': 'myapi git blob <repo> <ref> <path> [--org <id>]\n\nPrints the file content (decoded). --json gives {path, size, content_base64}.',
293
+ 'diff': 'myapi git diff <repo> --base <ref> --head <ref> [--org <id>]',
294
+ 'commit': `myapi git commit <repo> --branch <b> --message <m> --changes <json> [--base <ref>] [--author-name <n>] [--author-email <e>] [--org <id>]
295
+
296
+ --changes is a JSON array of file edits — each entry is
297
+ {path, content | content_base64 | delete:true, mode?}. Example:
298
+ myapi git commit my-repo --branch main --message "init" \\
299
+ --changes '[{"path":"README.md","content":"# Hello"}]'
300
+
301
+ --base is the expected current branch tip (optimistic concurrency);
302
+ omit it to create the branch, or pass it to guard against a stale write.`,
303
+ 'create-branch': 'myapi git create-branch <repo> <name> --from <ref> [--org <id>]',
304
+ 'delete-branch': 'myapi git delete-branch <repo> <branch> [--org <id>]',
305
+ 'tag': 'myapi git tag <repo> <name> --ref <ref> [--org <id>]',
306
+ 'merge': 'myapi git merge <repo> --target <branch> --source <branch> [--org <id>]\n\nFast-forward only.',
307
+ 'repack': 'myapi git repack <repo> [--org <id>]',
308
+ };
309
+ export async function run(subcommand, args, flags) {
310
+ if (!subcommand || (flags.help && !subcommand)) {
311
+ info(`Usage: myapi git <subcommand>
312
+
313
+ Hosted git repositories over HTTP — no local clone required.
314
+
315
+ Repositories:
316
+ create <name> Create a repository
317
+ list List repositories
318
+ get <repo> Show a repository's branch/tag counts
319
+ delete <repo> Delete a repository
320
+
321
+ History & content:
322
+ log <repo> List commits (--ref, --limit)
323
+ show <repo> <sha> Show a single commit
324
+ tree <repo> <ref> List a ref's file tree (--path)
325
+ blob <repo> <ref> <path> Print a file's content
326
+ diff <repo> Diff two refs (--base, --head)
327
+ refs <repo> List branches and tags
328
+
329
+ Writes:
330
+ commit <repo> Commit file changes (--branch, --message, --changes)
331
+ create-branch <repo> <name> Create a branch (--from <ref>)
332
+ delete-branch <repo> <branch> Delete a branch
333
+ tag <repo> <name> Create a tag (--ref <ref>)
334
+ merge <repo> Fast-forward merge (--target, --source)
335
+ repack <repo> Compact the repository's packfiles
336
+
337
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
338
+ return;
339
+ }
340
+ if (flags.help) {
341
+ const usage = SUBCOMMAND_USAGE[subcommand];
342
+ if (usage)
343
+ info(`Usage: ${usage}`);
344
+ else
345
+ info(`Unknown subcommand: ${subcommand}. Run "myapi git --help" for the list.`);
346
+ return;
347
+ }
348
+ switch (subcommand) {
349
+ case 'create': return create(args[0], flags);
350
+ case 'list': return list(flags);
351
+ case 'get': return get(args[0], flags);
352
+ case 'delete': return del(args[0], flags);
353
+ case 'refs': return refs(args[0], flags);
354
+ case 'log': return log(args[0], flags);
355
+ case 'show': return show(args[0], args[1], flags);
356
+ case 'tree': return tree(args[0], args[1], flags);
357
+ case 'blob': return blob(args[0], args[1], args[2], flags);
358
+ case 'diff': return diff(args[0], flags);
359
+ case 'commit': return commit(args[0], flags);
360
+ case 'create-branch': return createBranch(args[0], args[1], flags);
361
+ case 'delete-branch': return deleteBranch(args[0], args[1], flags);
362
+ case 'tag': return tag(args[0], args[1], flags);
363
+ case 'merge': return merge(args[0], flags);
364
+ case 'repack': return repack(args[0], flags);
365
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi git --help" for a list of valid subcommands.`);
366
+ }
367
+ }
@@ -135,9 +135,12 @@ export async function identity(pixelId, flags) {
135
135
  const config = requireConfig();
136
136
  const orgId = flags.org || config.default_org;
137
137
  if (!orgId || !pixelId) {
138
- error("Missing required arguments.\nUsage: myapi pixel identity <pixel_id> --org <id>\n(Or set defaults via: myapi config set-org <id>)");
138
+ error("Missing required arguments.\nUsage: myapi pixel identity <pixel_id> --website <domain> [--org <id>]\n(Or set defaults via: myapi config set-org <id>)");
139
139
  }
140
- const res = await sdkPixel.getIdentity(config.api_key, orgId, pixelId);
140
+ // Identity resolution is scoped to a website the org owns — required.
141
+ if (!flags.website)
142
+ error("Missing required arg: --website <domain>. Identity resolution is scoped to a host the org owns.");
143
+ const res = await sdkPixel.getIdentity(config.api_key, orgId, pixelId, flags.website);
141
144
  printJson(res);
142
145
  }
143
146
  // ── Dispatcher ───────────────────────────────────────────────────────────────
@@ -146,7 +149,7 @@ const SUBCOMMAND_USAGE = {
146
149
  'visits': 'myapi pixel visits --website <domain> [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--org <id>] [--json]',
147
150
  'events': 'myapi pixel events [--campaign-id <id>] [--domain <domain>] [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--org <id>] [--json]',
148
151
  'audience': 'myapi pixel audience [--org <id>]',
149
- 'identity': 'myapi pixel identity <pixel_id> [--org <id>]',
152
+ 'identity': 'myapi pixel identity <pixel_id> --website <domain> [--org <id>]',
150
153
  };
151
154
  export async function run(subcommand, args, flags) {
152
155
  if (!subcommand || (flags.help && !subcommand)) {
@@ -27,7 +27,7 @@ const BLOCK_END = `# end ${PROGRAM} completion`;
27
27
  // is missing here.
28
28
  export const COMMANDS = [
29
29
  'audience', 'auth', 'billing', 'company', 'completion', 'config', 'container',
30
- 'crm', 'database', 'domain', 'email', 'fn', 'funnel', 'help', 'image',
30
+ 'crm', 'database', 'domain', 'email', 'fn', 'funnel', 'git', 'help', 'image',
31
31
  'install-skills', 'keys', 'llm', 'org', 'payments', 'people', 'pixel',
32
32
  'setup', 'status', 'storage', 'update', 'url', 'webhook', 'whoami',
33
33
  'workflow',
@@ -58,6 +58,7 @@ export const SUBCOMMANDS = {
58
58
  fn: ['create', 'deploy', 'env', 'runs', 'list', 'get', 'delete'],
59
59
  payments: ['connect', 'status', 'charge', 'list', 'get', 'refund'],
60
60
  container: ['create', 'deploy', 'list', 'get', 'logs', 'delete'],
61
+ git: ['create', 'list', 'get', 'delete', 'refs', 'log', 'show', 'tree', 'blob', 'diff', 'commit', 'create-branch', 'delete-branch', 'tag', 'merge', 'repack'],
61
62
  completion: ['install', 'uninstall'],
62
63
  };
63
64
  // ── Completion request handling ──────────────────────────────────────────────
@@ -40,6 +40,7 @@ const COMMAND_MODULES = [
40
40
  './commands/fn.js',
41
41
  './commands/payments.js',
42
42
  './commands/container.js',
43
+ './commands/git.js',
43
44
  ];
44
45
  const ENDPOINT_PATTERN = /^(GET|POST|PATCH|PUT|DELETE) \/[A-Za-z0-9_\-./{}]*$/;
45
46
  describe('every CLI command exports a typed EXPOSES array (S-101)', () => {
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ import * as crmCmd from './commands/crm/index.js';
32
32
  import * as fnCmd from './commands/fn.js';
33
33
  import * as paymentsCmd from './commands/payments.js';
34
34
  import * as containerCmd from './commands/container.js';
35
+ import * as gitCmd from './commands/git.js';
35
36
  // Each command file declares the value flags it understands. We union them
36
37
  // into a single schema for the upfront parse, so adding a new value flag in
37
38
  // one command means editing one file (its SCHEMA), not a global allowlist.
@@ -60,6 +61,7 @@ const COMBINED_SCHEMA = {
60
61
  ...fnCmd.SCHEMA,
61
62
  ...paymentsCmd.SCHEMA,
62
63
  ...containerCmd.SCHEMA,
64
+ ...gitCmd.SCHEMA,
63
65
  // Top-level flags
64
66
  version: 'boolean',
65
67
  V: 'boolean',
@@ -219,6 +221,9 @@ async function main() {
219
221
  case 'container':
220
222
  await containerCmd.run(subcommand, restArgs, flags);
221
223
  break;
224
+ case 'git':
225
+ await gitCmd.run(subcommand, restArgs, flags);
226
+ break;
222
227
  // Convenience aliases
223
228
  case 'setup':
224
229
  await setupCmd.setup(flags);
@@ -342,6 +347,7 @@ const HELP_TARGETS = {
342
347
  fn: f => fnCmd.run(undefined, [], f),
343
348
  payments: f => paymentsCmd.run(undefined, [], f),
344
349
  container: f => containerCmd.run(undefined, [], f),
350
+ git: f => gitCmd.run(undefined, [], f),
345
351
  org: f => orgCmd.run(undefined, [], f),
346
352
  billing: f => billingCmd.run(undefined, [], f),
347
353
  keys: f => keysCmd.run(undefined, [], f),
@@ -385,6 +391,7 @@ Commands:
385
391
  funnel Manage websites (publish pages, custom domains, funnels)
386
392
  fn Create and deploy functions on the edge runtime
387
393
  container Run containers — services, workers, and scheduled jobs
394
+ git Hosted git repositories — repos, commits, branches, history
388
395
  payments Take payments with Stripe Checkout (connect, charge, refund)
389
396
  webhook Manage inbound webhook endpoints and inspect deliveries
390
397
  email Manage mailboxes, send/read email, templates, and campaigns
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ // SDK-level unit tests for async bulk email verification —
2
+ // email.verifyBulk + email.getVerifyJob. Mocks global fetch — no network.
3
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
4
+ import { email } from '@myapihq/sdk';
5
+ const API_KEY = 'myapi_test_abc';
6
+ const ORG = '11111111-1111-4111-8111-111111111111';
7
+ let fetchMock;
8
+ function ok(data) {
9
+ return new Response(JSON.stringify({ success: true, data, meta: {} }), {
10
+ status: 200, headers: { 'content-type': 'application/json' },
11
+ });
12
+ }
13
+ function fail(code, status) {
14
+ return new Response(JSON.stringify({ success: false, error: { code, message: code }, meta: {} }), {
15
+ status, headers: { 'content-type': 'application/json' },
16
+ });
17
+ }
18
+ beforeEach(() => { fetchMock = vi.fn(); globalThis.fetch = fetchMock; });
19
+ afterEach(() => { vi.restoreAllMocks(); });
20
+ describe('email.verifyBulk', () => {
21
+ it('POSTs {emails} and returns the job', async () => {
22
+ fetchMock.mockResolvedValueOnce(ok({ job_id: 'j1', status: 'pending', total: 2, catch_all: true }));
23
+ const job = await email.verifyBulk(API_KEY, ORG, ['a@x.com', 'b@y.com']);
24
+ const [url, init] = fetchMock.mock.calls[0];
25
+ expect(url).toContain(`/email/orgs/${ORG}/verify-bulk`);
26
+ expect(init.method).toBe('POST');
27
+ expect(JSON.parse(init.body)).toEqual({ emails: ['a@x.com', 'b@y.com'] });
28
+ expect(job.job_id).toBe('j1');
29
+ });
30
+ it('passes catch_all through when set', async () => {
31
+ fetchMock.mockResolvedValueOnce(ok({ job_id: 'j1', status: 'pending', total: 1 }));
32
+ await email.verifyBulk(API_KEY, ORG, ['a@x.com'], false);
33
+ expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ emails: ['a@x.com'], catch_all: false });
34
+ });
35
+ it('surfaces TOO_MANY_EMAILS (422)', async () => {
36
+ fetchMock.mockResolvedValueOnce(fail('TOO_MANY_EMAILS', 422));
37
+ await expect(email.verifyBulk(API_KEY, ORG, ['a@x.com']))
38
+ .rejects.toMatchObject({ code: 'TOO_MANY_EMAILS', status: 422 });
39
+ });
40
+ });
41
+ describe('email.getVerifyJob', () => {
42
+ it('GETs /verify-jobs/{id} and returns status + results', async () => {
43
+ fetchMock.mockResolvedValueOnce(ok({
44
+ job_id: 'j1', status: 'done', total: 1, completed: 1,
45
+ results: [{ email: 'a@x.com', verdict: 'deliverable', confidence: 0.9, source: 'smtp' }],
46
+ }));
47
+ const job = await email.getVerifyJob(API_KEY, ORG, 'j1');
48
+ expect(job.status).toBe('done');
49
+ expect(job.results?.[0].verdict).toBe('deliverable');
50
+ expect(fetchMock.mock.calls[0][0]).toContain(`/email/orgs/${ORG}/verify-jobs/j1`);
51
+ });
52
+ it('surfaces JOB_NOT_FOUND (404)', async () => {
53
+ fetchMock.mockResolvedValueOnce(fail('JOB_NOT_FOUND', 404));
54
+ await expect(email.getVerifyJob(API_KEY, ORG, 'nope'))
55
+ .rejects.toMatchObject({ code: 'JOB_NOT_FOUND', status: 404 });
56
+ });
57
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ // SDK-level unit tests for the git module. Verifies URL/body shape,
2
+ // envelope-unwrapping, path encoding, and error handling against
3
+ // myapi-hq/internal/routes/git/. Mocks global fetch — no network.
4
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
5
+ import { git } from '@myapihq/sdk';
6
+ const API_KEY = 'myapi_test_abc';
7
+ const ORG = '11111111-1111-4111-8111-111111111111';
8
+ let fetchMock;
9
+ function ok(data, status = 200) {
10
+ return new Response(JSON.stringify({ success: true, data, meta: {} }), {
11
+ status, headers: { 'content-type': 'application/json' },
12
+ });
13
+ }
14
+ function fail(code, status) {
15
+ return new Response(JSON.stringify({ success: false, error: { code, message: code }, meta: {} }), {
16
+ status, headers: { 'content-type': 'application/json' },
17
+ });
18
+ }
19
+ beforeEach(() => { fetchMock = vi.fn(); globalThis.fetch = fetchMock; });
20
+ afterEach(() => { vi.restoreAllMocks(); });
21
+ describe('git repos', () => {
22
+ it('createRepo POSTs {name, default_branch?}', async () => {
23
+ fetchMock.mockResolvedValueOnce(ok({ name: 'app', default_branch: 'main' }, 201));
24
+ await git.createRepo(API_KEY, ORG, 'app', 'trunk');
25
+ const [url, init] = fetchMock.mock.calls[0];
26
+ expect(url).toContain(`/git/orgs/${ORG}/repos`);
27
+ expect(init.method).toBe('POST');
28
+ expect(JSON.parse(init.body)).toEqual({ name: 'app', default_branch: 'trunk' });
29
+ });
30
+ it('createRepo omits default_branch when not given', async () => {
31
+ fetchMock.mockResolvedValueOnce(ok({ name: 'app', default_branch: 'main' }, 201));
32
+ await git.createRepo(API_KEY, ORG, 'app');
33
+ expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ name: 'app' });
34
+ });
35
+ it('listRepos unwraps the {repos:[...]} envelope to an array', async () => {
36
+ fetchMock.mockResolvedValueOnce(ok({ repos: [{ name: 'a' }, { name: 'b' }] }));
37
+ expect(await git.listRepos(API_KEY, ORG)).toEqual([{ name: 'a' }, { name: 'b' }]);
38
+ });
39
+ it('getRepo GETs /repos/{repo}', async () => {
40
+ fetchMock.mockResolvedValueOnce(ok({ name: 'app', default_branch: 'main', branches: 2, tags: 1 }));
41
+ const r = await git.getRepo(API_KEY, ORG, 'app');
42
+ expect(r.branches).toBe(2);
43
+ expect(fetchMock.mock.calls[0][0]).toContain(`/git/orgs/${ORG}/repos/app`);
44
+ });
45
+ it('deleteRepo DELETEs and surfaces 404', async () => {
46
+ fetchMock.mockResolvedValueOnce(new Response(null, { status: 204 }));
47
+ await git.deleteRepo(API_KEY, ORG, 'app');
48
+ expect(fetchMock.mock.calls[0][1].method).toBe('DELETE');
49
+ fetchMock.mockResolvedValueOnce(fail('repository not found', 404));
50
+ await expect(git.deleteRepo(API_KEY, ORG, 'gone')).rejects.toMatchObject({ status: 404 });
51
+ });
52
+ });
53
+ describe('git history & content', () => {
54
+ it('listCommits passes ref/limit as query and unwraps {commits}', async () => {
55
+ fetchMock.mockResolvedValueOnce(ok({ commits: [{ sha: 'abc', message: 'init' }] }));
56
+ const commits = await git.listCommits(API_KEY, ORG, 'app', { ref: 'dev', limit: 10 });
57
+ expect(commits).toHaveLength(1);
58
+ const url = fetchMock.mock.calls[0][0];
59
+ expect(url).toMatch(/\/commits\?/);
60
+ expect(url).toContain('ref=dev');
61
+ expect(url).toContain('limit=10');
62
+ });
63
+ it('getDiff requires base+head and unwraps {diff}', async () => {
64
+ fetchMock.mockResolvedValueOnce(ok({ diff: '--- a\n+++ b\n' }));
65
+ const d = await git.getDiff(API_KEY, ORG, 'app', 'main', 'dev');
66
+ expect(d).toBe('--- a\n+++ b\n');
67
+ const url = fetchMock.mock.calls[0][0];
68
+ expect(url).toContain('base=main');
69
+ expect(url).toContain('head=dev');
70
+ });
71
+ it('listTree unwraps {entries} and adds ?path when scoped', async () => {
72
+ fetchMock.mockResolvedValueOnce(ok({ entries: [{ name: 'a.js', type: 'file' }] }));
73
+ await git.listTree(API_KEY, ORG, 'app', 'main', 'src');
74
+ expect(fetchMock.mock.calls[0][0]).toMatch(/\/tree\/main\?path=src$/);
75
+ });
76
+ it('readBlob encodes path segments but keeps the slashes', async () => {
77
+ fetchMock.mockResolvedValueOnce(ok({ path: 'src/a b.js', size: 3, content_base64: 'eA==' }));
78
+ await git.readBlob(API_KEY, ORG, 'app', 'main', 'src/a b.js');
79
+ // slashes survive (catch-all route), spaces within a segment are encoded
80
+ expect(fetchMock.mock.calls[0][0]).toMatch(/\/blob\/main\/src\/a%20b\.js$/);
81
+ });
82
+ });
83
+ describe('git writes', () => {
84
+ it('commit POSTs the full payload', async () => {
85
+ fetchMock.mockResolvedValueOnce(ok({ sha: 'def', tree: 't', branch: 'main' }, 201));
86
+ const res = await git.commit(API_KEY, ORG, 'app', {
87
+ branch: 'main', message: 'add', changes: [{ path: 'x', content: 'hi' }],
88
+ });
89
+ expect(res.sha).toBe('def');
90
+ expect(JSON.parse(fetchMock.mock.calls[0][1].body).changes).toHaveLength(1);
91
+ });
92
+ it('createBranch POSTs {name, from}', async () => {
93
+ fetchMock.mockResolvedValueOnce(ok({ name: 'feature' }, 201));
94
+ await git.createBranch(API_KEY, ORG, 'app', 'feature', 'main');
95
+ expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ name: 'feature', from: 'main' });
96
+ });
97
+ it('merge POSTs {target, source} and surfaces NOT_FAST_FORWARD (422)', async () => {
98
+ fetchMock.mockResolvedValueOnce(ok({ sha: 'ff' }));
99
+ expect((await git.merge(API_KEY, ORG, 'app', 'main', 'dev')).sha).toBe('ff');
100
+ fetchMock.mockResolvedValueOnce(fail('NOT_FAST_FORWARD', 422));
101
+ await expect(git.merge(API_KEY, ORG, 'app', 'main', 'dev')).rejects.toMatchObject({ status: 422 });
102
+ });
103
+ it('repack POSTs and returns the pack counts', async () => {
104
+ fetchMock.mockResolvedValueOnce(ok({ packs_before: 5, packs_after: 1, objects: 200 }));
105
+ const r = await git.repack(API_KEY, ORG, 'app');
106
+ expect(r).toEqual({ packs_before: 5, packs_after: 1, objects: 200 });
107
+ });
108
+ });
109
+ describe('git.EXPOSES', () => {
110
+ it('covers all 16 git endpoints', () => {
111
+ expect(git.EXPOSES).toHaveLength(16);
112
+ expect(git.EXPOSES).toContain('POST /git/orgs/{org_id}/repos/{repo}/commits');
113
+ expect(git.EXPOSES).toContain('GET /git/orgs/{org_id}/repos/{repo}/blob/{ref}/{path}');
114
+ });
115
+ });
@@ -0,0 +1,45 @@
1
+ ---
2
+ # my-email-api
3
+
4
+ Send transactional email and run drip campaigns from mailboxes on your own registered domains. Includes AI template generation, warmup, and inbox/outbox reading.
5
+
6
+ ## What it does
7
+
8
+ - Create mailboxes on your registered domains
9
+ - Send transactional emails (one-shot or templated)
10
+ - Read inbox, outbox, sent history, and per-message status
11
+ - Generate HTML email templates with AI from a prompt
12
+ - Run paced drip campaigns against uploaded contact lists
13
+ - Manage IP/domain warmup for sender reputation
14
+
15
+ ## Quickstart
16
+
17
+ ```bash
18
+ # Create a mailbox + activate sending
19
+ myapi email mailbox create hello@yourdomain.com
20
+ myapi email mailbox activate-sending --address hello@yourdomain.com
21
+
22
+ # Send
23
+ myapi email message send \
24
+ --from hello@yourdomain.com \
25
+ --to recipient@example.com \
26
+ --subject "Hi" \
27
+ --body "Test"
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ ```bash
33
+ export MYAPI_KEY=mak_...
34
+ ```
35
+
36
+ Requires:
37
+ - An `api_key` from **myapihq**
38
+ - A registered domain via **mydomainapi**, assigned to your org
39
+ - Default `org_id` (for templates/campaigns) — set with `myapi auth config set-org <id>`
40
+
41
+ ## Documentation
42
+
43
+ Full command reference and flow diagrams: see `SKILL.md`.
44
+
45
+ Run `myapi email --help` for inline reference.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: my-email-api
3
+ version: 1.0.0
4
+ description: >
5
+ Send transactional and bulk email from your own domain. Create mailboxes, send/receive messages, generate AI templates, run drip campaigns, and manage warmup.
6
+ triggers: [email, mailbox, send email, transactional email, drip campaign, template, warmup, inbox, outbox, ses, sender reputation]
7
+ checksum: sha256-pending
8
+ ---
9
+
10
+ # MyEmailAPI
11
+
12
+ Account-scoped email infrastructure tied to your registered domains. Mailboxes belong to domains; sending and receiving work without per-org plumbing. Templates and campaigns are org-scoped.
13
+
14
+ ## Capabilities
15
+ <!-- llm:start -->
16
+ Email is built around mailboxes. Each mailbox lives on a registered domain (e.g. `hello@yourdomain.com`) and must be activated for sending before transactional sends or campaigns work — newly-created mailboxes can receive but not send.
17
+
18
+ Templates are AI-generated HTML emails (org-scoped). Campaigns combine a template + a contact list + a per-day rate limit, sending to recipients over time. Warmup is a separate flow that gradually ramps a mailbox's send rate to build inbox reputation before high-volume campaigns.
19
+
20
+ A registered domain via **mydomainapi** is the prerequisite — mailboxes need a domain to live on.
21
+ <!-- llm:end -->
22
+
23
+ ## Commands
24
+ <!-- generated:start -->
25
+ | Namespace | Subcommands | Purpose |
26
+ |---|---|---|
27
+ | `email mailbox` | `create`, `list`, `activate-sending` | Create and manage mailboxes |
28
+ | `email message` | `send`, `status`, `sent`, `inbox`, `outbox`, `get` | Transactional send + read |
29
+ | `email warmup` | `start`, `stats`, `pause`, `resume`, `stop` | IP/domain warmup for sending reputation |
30
+ | `email template` | `generate`, `list`, `get`, `preview`, `edit`, `send-test`, `delete` | AI-generated HTML templates |
31
+ | `email campaign` | `create`, `list`, `get`, `update`, `upload-contacts`, `upload-contacts-file`, `start`, `pause`, `resume`, `stats` | Drip campaigns |
32
+ <!-- generated:end -->
33
+
34
+ ## Examples
35
+ <!-- llm:start -->
36
+ ```bash
37
+ # 1. Create a mailbox + activate sending
38
+ myapi email mailbox create hello@yourdomain.com --display-name "Hello"
39
+ myapi email mailbox activate-sending --address hello@yourdomain.com
40
+
41
+ # 2. Send a transactional email
42
+ myapi email message send \
43
+ --from hello@yourdomain.com --to recipient@example.com \
44
+ --subject "Hi" --body "Test message"
45
+
46
+ # 3. Read inbox / outbox
47
+ myapi email message inbox hello@yourdomain.com
48
+ myapi email message outbox hello@yourdomain.com
49
+
50
+ # 4. Generate + use a template
51
+ myapi email template generate welcome-v1 \
52
+ --prompt "A welcome email with our brand colors and a CTA to /onboarding"
53
+ myapi email template list
54
+ myapi email template preview <id> # public preview URL — share with stakeholders
55
+ myapi email template send-test <id> --to me@yourdomain.com
56
+ myapi email template edit <id> --prompt "Make the CTA larger and red"
57
+
58
+ # 5. Drip campaign
59
+ myapi email campaign create "Welcome series" \
60
+ --template-id <id> --from hello@yourdomain.com --per-day 50
61
+ myapi email campaign upload-contacts <campaign_id> --emails "alice@x.com,bob@y.com"
62
+ # (or for big lists)
63
+ myapi email campaign upload-contacts-file <campaign_id> --file ./contacts.csv
64
+ myapi email campaign start <campaign_id>
65
+ myapi email campaign stats <campaign_id>
66
+
67
+ # 6. Warm up before a big send
68
+ myapi email warmup start --address hello@yourdomain.com
69
+ myapi email warmup stats --address hello@yourdomain.com
70
+ ```
71
+ <!-- llm:end -->
72
+
73
+ ## Notes
74
+
75
+ - A mailbox is uniquely identified by its address (`username@domain`).
76
+ - Sending is opt-in per mailbox. Newly-created mailboxes can receive but not send until `activate-sending` runs.
77
+ - Templates and campaigns are org-scoped. Set a default org once: `myapi config set-org <id>`.
78
+ - An active or paused campaign blocks `domain unassign` with `DOMAIN_IN_USE`.
79
+
80
+ Run `myapi email --help` or `myapi email <namespace> --help` for full flag reference.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "my-email-api",
3
+ "description": "Send transactional and bulk email from your own domain. Mailboxes, AI templates, drip campaigns, and warmup.",
4
+ "version": "1.0.0",
5
+ "published": true
6
+ }
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "1.2.8",
4
+ "version": "1.2.9",
5
5
  "description": "MyAPI command-line interface",
6
6
  "type": "module",
7
7
  "files": [
@@ -29,7 +29,7 @@
29
29
  "lint:changelog": "node ../../scripts/lint-changelog.js"
30
30
  },
31
31
  "dependencies": {
32
- "@myapihq/sdk": "^1.2.8"
32
+ "@myapihq/sdk": "^1.2.9"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^25.6.0",