@myapihq/cli 1.2.7 → 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.
- package/dist/commands/container.d.ts +1 -0
- package/dist/commands/container.js +28 -0
- package/dist/commands/email/index.js +1 -0
- package/dist/commands/email/verify.d.ts +2 -2
- package/dist/commands/email/verify.js +85 -16
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.js +367 -0
- package/dist/commands/pixel.js +6 -3
- package/dist/completion.js +3 -2
- package/dist/exposes.test.js +1 -0
- package/dist/index.js +7 -0
- package/dist/sdk-container.test.js +25 -1
- package/dist/sdk-email-verify-bulk.test.d.ts +1 -0
- package/dist/sdk-email-verify-bulk.test.js +57 -0
- package/dist/sdk-git.test.d.ts +1 -0
- package/dist/sdk-git.test.js +115 -0
- package/dist/skills/my-email-api/README.md +45 -0
- package/dist/skills/my-email-api/SKILL.md +80 -0
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
- package/package.json +2 -2
|
@@ -12,4 +12,5 @@ export declare function list(flags: Flags): Promise<void>;
|
|
|
12
12
|
export declare function get(id: string, flags: Flags): Promise<void>;
|
|
13
13
|
export declare function del(id: string, flags: Flags): Promise<void>;
|
|
14
14
|
export declare function deploy(id: string, image: string, flags: Flags): Promise<void>;
|
|
15
|
+
export declare function logs(id: string, flags: Flags): Promise<void>;
|
|
15
16
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -9,6 +9,7 @@ export const EXPOSES = [
|
|
|
9
9
|
'GET /container/orgs/{org_id}/containers/{id}',
|
|
10
10
|
'DELETE /container/orgs/{org_id}/containers/{id}',
|
|
11
11
|
'POST /container/orgs/{org_id}/containers/{id}/deploy',
|
|
12
|
+
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
12
13
|
];
|
|
13
14
|
export const SCHEMA = {
|
|
14
15
|
name: 'string',
|
|
@@ -20,6 +21,7 @@ export const SCHEMA = {
|
|
|
20
21
|
'max-instances': 'number',
|
|
21
22
|
port: 'number',
|
|
22
23
|
env: 'string',
|
|
24
|
+
tail: 'number',
|
|
23
25
|
};
|
|
24
26
|
const CONTAINER_TYPES = ['service', 'worker', 'job'];
|
|
25
27
|
// Mirrors validateName in myapi-hq/internal/routes/container/crud.go —
|
|
@@ -176,6 +178,26 @@ export async function deploy(id, image, flags) {
|
|
|
176
178
|
info(`Scoped API key was rotated. New value (returned once — save it if you need it):`);
|
|
177
179
|
info(` ${result.scoped_api_key}`);
|
|
178
180
|
}
|
|
181
|
+
// logs prints the container's recent Cloud Run runtime logs, newest first.
|
|
182
|
+
export async function logs(id, flags) {
|
|
183
|
+
const config = requireConfig();
|
|
184
|
+
const orgId = requireOrg(flags, config, 'myapi container logs <id> [--tail <n>] [--org <id>]');
|
|
185
|
+
if (!id)
|
|
186
|
+
error('Missing id.\nUsage: myapi container logs <id> [--tail <n>]');
|
|
187
|
+
const tail = typeof flags.tail === 'number' ? flags.tail : undefined;
|
|
188
|
+
const entries = await sdkContainer.getContainerLogs(config.api_key, orgId, id, tail);
|
|
189
|
+
if (flags.json) {
|
|
190
|
+
printJson(entries);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (entries.length === 0) {
|
|
194
|
+
info('No logs — the container is not deployed yet, or has produced no output.');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
for (const e of entries) {
|
|
198
|
+
info(`${e.timestamp} ${(e.severity || '').padEnd(8)} ${e.text}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
179
201
|
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
180
202
|
const SUBCOMMAND_USAGE = {
|
|
181
203
|
'create': `myapi container create --name <name> [--type service|worker|job] [--cron <expr>]
|
|
@@ -203,6 +225,10 @@ Example:
|
|
|
203
225
|
myapi container deploy <id> registry.example.com/my-app:v2`,
|
|
204
226
|
'list': 'myapi container list [--org <id>] [--json]',
|
|
205
227
|
'get': 'myapi container get <id> [--org <id>] [--json]',
|
|
228
|
+
'logs': `myapi container logs <id> [--tail <n>] [--org <id>] [--json]
|
|
229
|
+
|
|
230
|
+
Recent Cloud Run runtime logs, newest first. --tail caps the count
|
|
231
|
+
(default 100, max 1000).`,
|
|
206
232
|
'delete': 'myapi container delete <id> [--org <id>]',
|
|
207
233
|
};
|
|
208
234
|
export async function run(subcommand, args, flags) {
|
|
@@ -218,6 +244,7 @@ Subcommands:
|
|
|
218
244
|
deploy <id> <image> Ship a pre-built image and go live
|
|
219
245
|
list List containers in your org
|
|
220
246
|
get <id> Inspect a container
|
|
247
|
+
logs <id> Show recent runtime logs (--tail <n>)
|
|
221
248
|
delete <id> Soft-delete and revoke its scoped API key
|
|
222
249
|
|
|
223
250
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
@@ -236,6 +263,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
|
236
263
|
case 'deploy': return deploy(args[0], args[1], flags);
|
|
237
264
|
case 'list': return list(flags);
|
|
238
265
|
case 'get': return get(args[0], flags);
|
|
266
|
+
case 'logs': return logs(args[0], flags);
|
|
239
267
|
case 'delete': return del(args[0], flags);
|
|
240
268
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi container --help" for a list of valid subcommands.`);
|
|
241
269
|
}
|
|
@@ -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\
|
|
7
|
-
export declare function run(
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/commands/pixel.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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)) {
|
package/dist/completion.js
CHANGED
|
@@ -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',
|
|
@@ -57,7 +57,8 @@ export const SUBCOMMANDS = {
|
|
|
57
57
|
config: ['view', 'set-org', 'set-funnel', 'set-domain'],
|
|
58
58
|
fn: ['create', 'deploy', 'env', 'runs', 'list', 'get', 'delete'],
|
|
59
59
|
payments: ['connect', 'status', 'charge', 'list', 'get', 'refund'],
|
|
60
|
-
container: ['create', 'deploy', 'list', 'get', 'delete'],
|
|
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 ──────────────────────────────────────────────
|
package/dist/exposes.test.js
CHANGED
|
@@ -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
|
|
@@ -102,14 +102,38 @@ describe('container.deployContainer', () => {
|
|
|
102
102
|
.rejects.toMatchObject({ code: 'IMAGE_REQUIRED', status: 422 });
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
describe('container.getContainerLogs', () => {
|
|
106
|
+
it('GETs /logs and returns the entries', async () => {
|
|
107
|
+
fetchMock.mockResolvedValueOnce(ok([
|
|
108
|
+
{ timestamp: 't2', severity: 'INFO', text: 'started' },
|
|
109
|
+
{ timestamp: 't1', severity: 'ERROR', text: 'boom' },
|
|
110
|
+
]));
|
|
111
|
+
const entries = await container.getContainerLogs(API_KEY, ORG_ID, C_ID);
|
|
112
|
+
expect(entries).toHaveLength(2);
|
|
113
|
+
expect(entries[1].severity).toBe('ERROR');
|
|
114
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
115
|
+
expect(url).toContain(`/container/orgs/${ORG_ID}/containers/${C_ID}/logs`);
|
|
116
|
+
expect(init.method).toBe('GET');
|
|
117
|
+
});
|
|
118
|
+
it('appends ?tail=N when a tail count is given', async () => {
|
|
119
|
+
fetchMock.mockResolvedValueOnce(ok([]));
|
|
120
|
+
await container.getContainerLogs(API_KEY, ORG_ID, C_ID, 250);
|
|
121
|
+
expect(fetchMock.mock.calls[0][0]).toMatch(/\/logs\?tail=250$/);
|
|
122
|
+
});
|
|
123
|
+
it('returns an empty array for an undeployed container', async () => {
|
|
124
|
+
fetchMock.mockResolvedValueOnce(ok([]));
|
|
125
|
+
expect(await container.getContainerLogs(API_KEY, ORG_ID, C_ID)).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
105
128
|
describe('container.EXPOSES', () => {
|
|
106
|
-
it('covers the
|
|
129
|
+
it('covers the 6 container endpoints', () => {
|
|
107
130
|
expect(container.EXPOSES).toEqual([
|
|
108
131
|
'POST /container/orgs/{org_id}/containers',
|
|
109
132
|
'GET /container/orgs/{org_id}/containers',
|
|
110
133
|
'GET /container/orgs/{org_id}/containers/{id}',
|
|
111
134
|
'DELETE /container/orgs/{org_id}/containers/{id}',
|
|
112
135
|
'POST /container/orgs/{org_id}/containers/{id}/deploy',
|
|
136
|
+
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
113
137
|
]);
|
|
114
138
|
});
|
|
115
139
|
});
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
32
|
+
"@myapihq/sdk": "^1.2.9"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.6.0",
|