@myapihq/cli 1.3.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +356 -18
- package/dist/commands/authproduct.d.ts +7 -0
- package/dist/commands/authproduct.js +286 -0
- package/dist/commands/billing.js +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/container.d.ts +1 -0
- package/dist/commands/container.js +115 -11
- package/dist/commands/domain.js +2 -2
- package/dist/commands/email/index.js +0 -13
- package/dist/commands/fn.d.ts +1 -0
- package/dist/commands/fn.js +64 -12
- package/dist/commands/keys.js +3 -3
- package/dist/commands/queue.d.ts +1 -0
- package/dist/commands/queue.js +10 -0
- package/dist/commands/setup.js +8 -8
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +11 -27
- package/dist/commands/storage.js +3 -1
- package/dist/commands/workflow.js +21 -6
- package/dist/completion.js +5 -4
- package/dist/config.js +1 -1
- package/dist/exposes.test.js +1 -2
- package/dist/index.js +31 -28
- package/dist/registrant.js +5 -5
- package/dist/sdk-queue.test.js +3 -2
- package/dist/skills/my-api-hq/SKILL.md +15 -12
- package/dist/skills/my-auth-api/README.md +33 -0
- package/dist/skills/my-auth-api/SKILL.md +112 -0
- package/dist/skills/my-auth-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-auth-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-crm-api/SKILL.md +1 -1
- package/dist/skills/my-domain-api/SKILL.md +4 -4
- package/dist/skills/my-email-api/README.md +1 -1
- package/dist/skills/my-email-api/SKILL.md +7 -18
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +1 -1
- package/dist/skills/my-email-verify-api/SKILL.md +1 -1
- package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +1 -1
- package/dist/skills/my-git-api/README.md +43 -0
- package/dist/skills/my-git-api/SKILL.md +115 -0
- package/dist/skills/my-git-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-git-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +1 -1
- package/package.json +5 -4
- package/dist/commands/auth.d.ts +0 -11
- package/dist/commands/auth.js +0 -345
- package/dist/commands/email/campaign.d.ts +0 -4
- package/dist/commands/email/campaign.js +0 -200
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { auth as sdkAuth } from '@myapihq/sdk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { success, error, info, printTable, printJson } from '../output.js';
|
|
4
|
+
import { requireOrg } from '../helpers.js';
|
|
5
|
+
// `myapi auth` — the END-USER auth product (my-auth-api): a managed OIDC IdP
|
|
6
|
+
// for the users of apps built on MyAPI. Operator/account auth lives under
|
|
7
|
+
// `myapi account` (see commands/auth.ts + index.ts dispatch). The machine
|
|
8
|
+
// OIDC endpoints (discovery/jwks/authorize/token/userinfo) are consumed by
|
|
9
|
+
// apps + the JS SDK, not the CLI, so only the management surface is here.
|
|
10
|
+
export const SCHEMA = {
|
|
11
|
+
name: 'string',
|
|
12
|
+
type: 'string', // spa | web
|
|
13
|
+
redirect: 'string', // comma-separated redirect URIs
|
|
14
|
+
theme: 'string', // JSON for the hosted login page
|
|
15
|
+
connections: 'string', // comma-separated sign-in methods: google,password,magic
|
|
16
|
+
domain: 'string', // custom auth domain, e.g. auth.acme.com
|
|
17
|
+
};
|
|
18
|
+
export const EXPOSES = [
|
|
19
|
+
'POST /auth/orgs/{org_id}/tenant',
|
|
20
|
+
'GET /auth/orgs/{org_id}/tenant',
|
|
21
|
+
'POST /auth/orgs/{org_id}/clients',
|
|
22
|
+
'GET /auth/orgs/{org_id}/clients',
|
|
23
|
+
'GET /auth/orgs/{org_id}/usage',
|
|
24
|
+
'POST /auth/orgs/{org_id}/domain',
|
|
25
|
+
'GET /auth/orgs/{org_id}/domain',
|
|
26
|
+
'DELETE /auth/orgs/{org_id}/domain',
|
|
27
|
+
];
|
|
28
|
+
const SUBCOMMAND_USAGE = {
|
|
29
|
+
'tenant': `myapi auth tenant [show|create] [--connections <list>] [--theme <json>] [--org <id>] [--json]
|
|
30
|
+
|
|
31
|
+
Your org's OIDC auth tenant — the identity provider your app's end users sign
|
|
32
|
+
in against. One per org.
|
|
33
|
+
|
|
34
|
+
myapi auth tenant Show the tenant (issuer + hosted login URL)
|
|
35
|
+
myapi auth tenant create Create/enable it (idempotent)
|
|
36
|
+
|
|
37
|
+
--connections <list> Sign-in methods on the hosted login page, comma-separated:
|
|
38
|
+
google,password,magic (default: google).
|
|
39
|
+
--theme <json> Branding JSON for the hosted login page.`,
|
|
40
|
+
'usage': `myapi auth usage [--org <id>] [--json]
|
|
41
|
+
|
|
42
|
+
Monthly active users (MAU) for the current period — auth is billed per MAU.`,
|
|
43
|
+
'domain': `myapi auth domain [show|set|delete] [--domain <host>] [--org <id>] [--json]
|
|
44
|
+
|
|
45
|
+
Serve auth on your own domain (e.g. auth.acme.com) instead of the default issuer.
|
|
46
|
+
|
|
47
|
+
myapi auth domain Show the current custom domain + status
|
|
48
|
+
myapi auth domain set --domain auth.acme.com Register it (prints the DNS record to create)
|
|
49
|
+
myapi auth domain delete Remove the custom domain
|
|
50
|
+
|
|
51
|
+
After 'set', create the printed DNS A record; TLS provisions automatically
|
|
52
|
+
(~30 min) and the domain becomes your issuer once active.`,
|
|
53
|
+
'client': `myapi auth client <list|create> [--org <id>] [--json]
|
|
54
|
+
|
|
55
|
+
OIDC clients are the apps that authenticate against your tenant.
|
|
56
|
+
|
|
57
|
+
myapi auth client list
|
|
58
|
+
myapi auth client create --name "My App" --type spa --redirect https://app.example.com/callback
|
|
59
|
+
|
|
60
|
+
--name <n> Human label for the app (required)
|
|
61
|
+
--type spa|web spa = public (no secret); web = confidential (secret once)
|
|
62
|
+
--redirect <urls> Allowed redirect URIs, comma-separated (required).
|
|
63
|
+
Absolute https (or http://localhost for dev).`,
|
|
64
|
+
};
|
|
65
|
+
export const HELP = `Usage: myapi auth <subcommand>
|
|
66
|
+
|
|
67
|
+
Authentication for your app's end users — a managed OIDC identity provider
|
|
68
|
+
(à la Kinde/Auth0). One auth tenant per org; register OIDC clients (apps)
|
|
69
|
+
against it; sign users in via the hosted login page or the JS SDK; verify
|
|
70
|
+
tokens locally against the tenant JWKS.
|
|
71
|
+
|
|
72
|
+
End users sign in with managed Google, email/password, or magic links — pick
|
|
73
|
+
which via \`tenant create --connections\`. (Operator/account commands moved to
|
|
74
|
+
\`myapi account\`.)
|
|
75
|
+
|
|
76
|
+
Subcommands:
|
|
77
|
+
client Register and list OIDC clients (your apps)
|
|
78
|
+
domain Serve auth on your own domain (auth.acme.com)
|
|
79
|
+
tenant Show or create your org's OIDC auth tenant (+ sign-in methods)
|
|
80
|
+
usage Monthly active users (MAU) for the current period`;
|
|
81
|
+
export async function run(subcommand, args, flags) {
|
|
82
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
83
|
+
info(HELP);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (flags.help) {
|
|
87
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
88
|
+
info(usage ? `Usage: ${usage}` : `Unknown subcommand "${subcommand}". Run "myapi auth --help".`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
switch (subcommand) {
|
|
92
|
+
case 'tenant': return tenant(args, flags);
|
|
93
|
+
case 'client': return client(args, flags);
|
|
94
|
+
case 'usage': return usage(flags);
|
|
95
|
+
case 'domain': return domain(args, flags);
|
|
96
|
+
default: error(`Unknown subcommand "${subcommand}". Run "myapi auth --help" for tenant/client/usage/domain.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function tenant(args, flags) {
|
|
100
|
+
const action = args[0] || 'show';
|
|
101
|
+
const config = requireConfig();
|
|
102
|
+
const orgId = requireOrg(flags, config, 'myapi auth tenant [show|create] [--org <id>]');
|
|
103
|
+
if (action === 'show') {
|
|
104
|
+
try {
|
|
105
|
+
const t = await sdkAuth.getTenant(config.api_key, orgId);
|
|
106
|
+
if (flags.json) {
|
|
107
|
+
printJson(t);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
info(`Auth tenant: ${t.tenant_id}`);
|
|
111
|
+
info(`Issuer: ${t.issuer}`);
|
|
112
|
+
info(`Login URL: ${t.login_url}`);
|
|
113
|
+
if (t.connections?.length)
|
|
114
|
+
info(`Sign-in: ${t.connections.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (e?.code === 'TENANT_NOT_FOUND' || e?.status === 404) {
|
|
118
|
+
error('No auth tenant yet for this org.\nCreate it with: myapi auth tenant create');
|
|
119
|
+
}
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (action === 'create' || action === 'init') {
|
|
125
|
+
let theme;
|
|
126
|
+
if (flags.theme) {
|
|
127
|
+
try {
|
|
128
|
+
theme = JSON.parse(flags.theme);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
error('--theme must be valid JSON');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let connections;
|
|
135
|
+
if (flags.connections) {
|
|
136
|
+
const allowed = ['google', 'password', 'magic'];
|
|
137
|
+
connections = flags.connections.split(',').map(s => s.trim()).filter(Boolean);
|
|
138
|
+
const bad = connections.filter(c => !allowed.includes(c));
|
|
139
|
+
if (bad.length)
|
|
140
|
+
error(`--connections may only contain google, password, magic (got: ${bad.join(', ')})`);
|
|
141
|
+
}
|
|
142
|
+
const t = await sdkAuth.createTenant(config.api_key, orgId, { theme, connections });
|
|
143
|
+
if (flags.json) {
|
|
144
|
+
printJson(t);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
success(`Auth tenant ready: ${t.tenant_id}`);
|
|
148
|
+
info(`Issuer: ${t.issuer}`);
|
|
149
|
+
info(`Login URL: ${t.login_url}`);
|
|
150
|
+
if (t.connections?.length)
|
|
151
|
+
info(`Sign-in: ${t.connections.join(', ')}`);
|
|
152
|
+
info('Next: register an app → myapi auth client create --name <n> --type spa --redirect <url>');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
error(`Unknown action "${action}". Use: myapi auth tenant [show|create]`);
|
|
156
|
+
}
|
|
157
|
+
async function usage(flags) {
|
|
158
|
+
const config = requireConfig();
|
|
159
|
+
const orgId = requireOrg(flags, config, 'myapi auth usage [--org <id>]');
|
|
160
|
+
const u = await sdkAuth.getUsage(config.api_key, orgId);
|
|
161
|
+
if (flags.json) {
|
|
162
|
+
printJson(u);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
info(`Period: ${u.period}`);
|
|
166
|
+
info(`Active users: ${u.active_users} MAU`);
|
|
167
|
+
info(`Price: $${(u.price_cents_each / 100).toFixed(2)} per MAU`);
|
|
168
|
+
}
|
|
169
|
+
async function domain(args, flags) {
|
|
170
|
+
const action = args[0] || 'show';
|
|
171
|
+
const config = requireConfig();
|
|
172
|
+
const orgId = requireOrg(flags, config, 'myapi auth domain [show|set|delete] [--org <id>]');
|
|
173
|
+
const showDomain = (d) => {
|
|
174
|
+
info(`Domain: ${d.domain}`);
|
|
175
|
+
info(`Status: ${d.status}`);
|
|
176
|
+
if (d.status !== 'active') {
|
|
177
|
+
info('');
|
|
178
|
+
info(`DNS — create this record:`);
|
|
179
|
+
info(` ${d.dns.type} ${d.dns.name} → ${d.dns.value}`);
|
|
180
|
+
if (d.next)
|
|
181
|
+
info(`\n${d.next}`);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
info(`Issuer: ${d.issuer}`);
|
|
185
|
+
info(`Login URL: ${d.login_url}`);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
if (action === 'show') {
|
|
189
|
+
try {
|
|
190
|
+
const d = await sdkAuth.getDomain(config.api_key, orgId);
|
|
191
|
+
if (flags.json) {
|
|
192
|
+
printJson(d);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
showDomain(d);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
if (e?.code === 'NO_DOMAIN' || e?.status === 404) {
|
|
199
|
+
error('No custom auth domain set.\nSet one with: myapi auth domain set --domain auth.example.com');
|
|
200
|
+
}
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (action === 'set' || action === 'register') {
|
|
206
|
+
const host = flags.domain || args[1] || '';
|
|
207
|
+
if (!host)
|
|
208
|
+
error('--domain is required (e.g. --domain auth.example.com)');
|
|
209
|
+
const d = await sdkAuth.registerDomain(config.api_key, orgId, host);
|
|
210
|
+
if (flags.json) {
|
|
211
|
+
printJson(d);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
success(`Custom auth domain registered: ${d.domain} (${d.status})`);
|
|
215
|
+
showDomain(d);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (action === 'delete' || action === 'remove') {
|
|
219
|
+
const host = flags.domain || args[1];
|
|
220
|
+
let target = host;
|
|
221
|
+
if (!target) {
|
|
222
|
+
// No host given — resolve the current one so the user doesn't have to retype it.
|
|
223
|
+
try {
|
|
224
|
+
target = (await sdkAuth.getDomain(config.api_key, orgId)).domain;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
error('No custom auth domain to delete.');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
await sdkAuth.deleteDomain(config.api_key, orgId, target);
|
|
231
|
+
success(`Custom auth domain removed: ${target}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
error(`Unknown action "${action}". Use: myapi auth domain [show|set|delete]`);
|
|
235
|
+
}
|
|
236
|
+
async function client(args, flags) {
|
|
237
|
+
const action = args[0] || 'list';
|
|
238
|
+
const config = requireConfig();
|
|
239
|
+
const orgId = requireOrg(flags, config, 'myapi auth client <list|create> [--org <id>]');
|
|
240
|
+
if (action === 'list') {
|
|
241
|
+
const res = await sdkAuth.listClients(config.api_key, orgId);
|
|
242
|
+
if (flags.json) {
|
|
243
|
+
printJson(res);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
printTable((res.clients || []).map(c => ({
|
|
247
|
+
ClientID: c.client_id,
|
|
248
|
+
Name: c.name || '',
|
|
249
|
+
Type: c.type,
|
|
250
|
+
Redirects: (c.redirect_uris || []).join(', '),
|
|
251
|
+
})), {
|
|
252
|
+
flags,
|
|
253
|
+
empty: 'No OIDC clients yet.\nCreate one: myapi auth client create --name <n> --type spa --redirect <url>',
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (action === 'create') {
|
|
258
|
+
const name = flags.name || '';
|
|
259
|
+
const type = flags.type;
|
|
260
|
+
const redirect = flags.redirect || '';
|
|
261
|
+
if (!name)
|
|
262
|
+
error('--name is required (e.g. --name "My App")');
|
|
263
|
+
if (type !== 'spa' && type !== 'web')
|
|
264
|
+
error("--type must be 'spa' (public) or 'web' (confidential)");
|
|
265
|
+
if (!redirect)
|
|
266
|
+
error('--redirect is required (comma-separate multiple URIs)');
|
|
267
|
+
const redirect_uris = redirect.split(',').map(s => s.trim()).filter(Boolean);
|
|
268
|
+
const c = await sdkAuth.createClient(config.api_key, orgId, { name, type: type, redirect_uris });
|
|
269
|
+
if (flags.json) {
|
|
270
|
+
printJson(c);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
success(`Client created: ${c.client_id}`);
|
|
274
|
+
info(`Type: ${c.type}`);
|
|
275
|
+
info(`Redirects: ${(c.redirect_uris || []).join(', ')}`);
|
|
276
|
+
if (c.issuer)
|
|
277
|
+
info(`Issuer: ${c.issuer}`);
|
|
278
|
+
if (c.client_secret) {
|
|
279
|
+
info('');
|
|
280
|
+
info('Client secret (shown ONCE — store it now, it cannot be retrieved again):');
|
|
281
|
+
info(` ${c.client_secret}`);
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
error(`Unknown action "${action}". Use: myapi auth client <list|create>`);
|
|
286
|
+
}
|
package/dist/commands/billing.js
CHANGED
|
@@ -92,7 +92,7 @@ export async function balance(flags) {
|
|
|
92
92
|
// Anonymous accounts can't transact — paid surface is gated on a
|
|
93
93
|
// verified email and credits only fund on upgrade. Surface the unblock.
|
|
94
94
|
if (config.is_anonymous) {
|
|
95
|
-
info('→ Link an email to unlock $5 free credit + paid actions: myapi
|
|
95
|
+
info('→ Link an email to unlock $5 free credit + paid actions: myapi account link <email>');
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
export async function history(flags) {
|
package/dist/commands/config.js
CHANGED
|
@@ -49,7 +49,7 @@ export async function setFunnel(id, _flags, via = 'auth config') {
|
|
|
49
49
|
const config = requireConfig();
|
|
50
50
|
const orgId = config.default_org;
|
|
51
51
|
if (!orgId)
|
|
52
|
-
error('No default organization set. Run: myapi
|
|
52
|
+
error('No default organization set. Run: myapi account config set-org <id>');
|
|
53
53
|
info('› Validating…');
|
|
54
54
|
const { funnel } = await sdkFunnel.getFunnel(config.api_key, orgId, id);
|
|
55
55
|
const funnelId = funnel.id;
|
|
@@ -11,6 +11,7 @@ export declare function create(flags: Flags): Promise<void>;
|
|
|
11
11
|
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
|
+
export declare function _isTarball(p: string): boolean;
|
|
14
15
|
export declare function deploy(id: string, image: string, flags: Flags): Promise<void>;
|
|
15
16
|
export declare function logs(id: string, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function domain(id: string, domainArg: string | undefined, flags: Flags): Promise<void>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
1
3
|
import { container as sdkContainer } from '@myapihq/sdk';
|
|
2
4
|
import { requireConfig } from '../config.js';
|
|
3
5
|
import { success, error, printTable, info, printJson, banner } from '../output.js';
|
|
4
|
-
import { formatDate } from '../utils.js';
|
|
6
|
+
import { formatDate, pollJob } from '../utils.js';
|
|
5
7
|
import { requireOrg } from '../helpers.js';
|
|
6
8
|
export const EXPOSES = [
|
|
7
9
|
'POST /container/orgs/{org_id}/containers',
|
|
@@ -25,6 +27,8 @@ export const SCHEMA = {
|
|
|
25
27
|
env: 'string',
|
|
26
28
|
tail: 'number',
|
|
27
29
|
remove: 'boolean',
|
|
30
|
+
source: 'string',
|
|
31
|
+
image: 'string',
|
|
28
32
|
};
|
|
29
33
|
const CONTAINER_TYPES = ['service', 'worker', 'job'];
|
|
30
34
|
// Mirrors validateName in myapi-hq/internal/routes/container/crud.go —
|
|
@@ -149,6 +153,8 @@ export async function get(id, flags) {
|
|
|
149
153
|
if (c.port)
|
|
150
154
|
info(`Port: ${c.port}`);
|
|
151
155
|
info(`URL: ${c.url || '(not deployed)'}`);
|
|
156
|
+
if (c.egress)
|
|
157
|
+
info(`Egress: ${c.egress}`);
|
|
152
158
|
if (c.custom_domain)
|
|
153
159
|
info(`Custom domain: ${c.custom_domain}`);
|
|
154
160
|
info(`Created: ${c.created_at}`);
|
|
@@ -162,15 +168,106 @@ export async function del(id, flags) {
|
|
|
162
168
|
await sdkContainer.deleteContainer(config.api_key, orgId, id);
|
|
163
169
|
success(`Deleted container ${id}`);
|
|
164
170
|
}
|
|
165
|
-
//
|
|
166
|
-
//
|
|
171
|
+
// _isTarball returns true if the path looks like an already-built tar archive
|
|
172
|
+
// (so we upload it as-is instead of tarring a directory).
|
|
173
|
+
export function _isTarball(p) {
|
|
174
|
+
return /\.(tar|tar\.gz|tgz)$/i.test(p);
|
|
175
|
+
}
|
|
176
|
+
// tarDirectory builds a gzipped tarball of a build-context directory using the
|
|
177
|
+
// system `tar`. Returns the archive bytes. We tar the directory contents (-C
|
|
178
|
+
// <dir> .) so the Dockerfile sits at the root of the build context.
|
|
179
|
+
async function tarDirectory(dir) {
|
|
180
|
+
const res = spawnSync('tar', ['-czf', '-', '-C', dir, '.'], {
|
|
181
|
+
maxBuffer: 200 * 1024 * 1024,
|
|
182
|
+
});
|
|
183
|
+
if (res.error) {
|
|
184
|
+
if (res.error.code === 'ENOENT') {
|
|
185
|
+
error('`tar` was not found on PATH. Install tar, or pass a pre-built .tar.gz to --source.');
|
|
186
|
+
}
|
|
187
|
+
error(`Failed to create build-context archive: ${res.error.message}`);
|
|
188
|
+
}
|
|
189
|
+
if (res.status !== 0) {
|
|
190
|
+
error(`tar exited ${res.status}: ${res.stderr?.toString().trim() || 'unknown error'}`);
|
|
191
|
+
}
|
|
192
|
+
return res.stdout;
|
|
193
|
+
}
|
|
194
|
+
// deploy ships either a pre-built image (--image / positional ref, synchronous)
|
|
195
|
+
// or a source build context (--source <dir-or-tar>, asynchronous Cloud Build).
|
|
196
|
+
// The scoped API key is rotated on every (image) deploy and shown once.
|
|
167
197
|
export async function deploy(id, image, flags) {
|
|
168
198
|
const config = requireConfig();
|
|
169
|
-
const orgId = requireOrg(flags, config, 'myapi container deploy <id> <image-ref> [--org <id>]');
|
|
199
|
+
const orgId = requireOrg(flags, config, 'myapi container deploy <id> <image-ref> | --source <dir|tar> [--org <id>]');
|
|
170
200
|
if (!id)
|
|
171
|
-
error('Missing id.\nUsage: myapi container deploy <id> <image-ref>');
|
|
201
|
+
error('Missing id.\nUsage: myapi container deploy <id> <image-ref> (or --source <dir|tar>)');
|
|
202
|
+
const source = typeof flags.source === 'string' ? flags.source : undefined;
|
|
203
|
+
// --image is an alias for the positional image ref.
|
|
204
|
+
if (!image && typeof flags.image === 'string')
|
|
205
|
+
image = flags.image;
|
|
206
|
+
if (source && image) {
|
|
207
|
+
error('Pass either an image ref or --source, not both.');
|
|
208
|
+
}
|
|
209
|
+
// ── Source-build path (async) ───────────────────────────────────────────
|
|
210
|
+
if (source) {
|
|
211
|
+
let tarball;
|
|
212
|
+
let filename = 'source.tar.gz';
|
|
213
|
+
let info_st;
|
|
214
|
+
try {
|
|
215
|
+
info_st = await stat(source);
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
if (e?.code === 'ENOENT')
|
|
219
|
+
error(`Source not found: ${source}`);
|
|
220
|
+
error(`Could not read ${source}: ${e?.message ?? e}`);
|
|
221
|
+
}
|
|
222
|
+
if (info_st.isDirectory()) {
|
|
223
|
+
tarball = await tarDirectory(source);
|
|
224
|
+
}
|
|
225
|
+
else if (_isTarball(source)) {
|
|
226
|
+
tarball = await readFile(source);
|
|
227
|
+
filename = source.split('/').pop() || filename;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
error(`--source must be a directory or a .tar/.tar.gz/.tgz archive — got ${source}`);
|
|
231
|
+
}
|
|
232
|
+
const start = await sdkContainer.deployContainerSource(config.api_key, orgId, id, tarball, filename);
|
|
233
|
+
if (flags.json && start.status !== 'building') {
|
|
234
|
+
printJson(start);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Async: poll the container until it leaves the building state.
|
|
238
|
+
if (start.status === 'building') {
|
|
239
|
+
info(`Build started (revision ${start.revision_id}). Building from source via Cloud Build…`);
|
|
240
|
+
const final = await pollJob({
|
|
241
|
+
label: 'Building & deploying',
|
|
242
|
+
check: () => sdkContainer.getContainer(config.api_key, orgId, id),
|
|
243
|
+
isDone: (c) => c.status === 'active',
|
|
244
|
+
isFailed: (c) => c.status === 'build_error' || c.status === 'deploy_error',
|
|
245
|
+
failedMessage: 'Build/deploy failed — check `myapi container logs ' + id + '`.',
|
|
246
|
+
timeoutMs: 600_000,
|
|
247
|
+
intervalMs: 5_000,
|
|
248
|
+
});
|
|
249
|
+
if (flags.json) {
|
|
250
|
+
printJson(final);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
success(`Deployed container ${id} from source`);
|
|
254
|
+
info(`Status: ${final.status}`);
|
|
255
|
+
info(`URL: ${final.url || '(none — job container)'}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Backend deployed synchronously after all (unexpected for source) —
|
|
259
|
+
// surface whatever it returned.
|
|
260
|
+
if (flags.json) {
|
|
261
|
+
printJson(start);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
success(`Deployed container ${id} (revision ${start.revision_id})`);
|
|
265
|
+
info(`Status: ${start.status}`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// ── Pre-built image path (sync) ─────────────────────────────────────────
|
|
172
269
|
if (!image)
|
|
173
|
-
error('Missing image ref.\nUsage: myapi container deploy <id> <image-ref>\n\n→ <image-ref> is a pre-built container image (e.g. a registry path).');
|
|
270
|
+
error('Missing image ref.\nUsage: myapi container deploy <id> <image-ref>\n or: myapi container deploy <id> --source <dir|tar>\n\n→ <image-ref> is a pre-built container image (e.g. a registry path).');
|
|
174
271
|
const result = await sdkContainer.deployContainer(config.api_key, orgId, id, image);
|
|
175
272
|
if (flags.json) {
|
|
176
273
|
printJson(result);
|
|
@@ -247,12 +344,19 @@ Examples:
|
|
|
247
344
|
myapi container create --name nightly --type job --cron "0 3 * * *"
|
|
248
345
|
myapi container create --name queue-worker --type worker --memory 1Gi`,
|
|
249
346
|
'deploy': `myapi container deploy <id> <image-ref> [--org <id>] [--json]
|
|
347
|
+
myapi container deploy <id> --source <dir|tar.gz> [--org <id>] [--json]
|
|
250
348
|
|
|
251
|
-
|
|
252
|
-
|
|
349
|
+
Two ways to deploy:
|
|
350
|
+
image Ship a pre-built image (positional ref or --image). Synchronous;
|
|
351
|
+
the scoped API key is rotated and printed once.
|
|
352
|
+
source Upload a build context with --source <dir> (tarred locally) or a
|
|
353
|
+
pre-built --source <archive.tar.gz>. MyAPI builds it via Cloud
|
|
354
|
+
Build, then deploys. Asynchronous — the CLI polls until it's live.
|
|
253
355
|
|
|
254
|
-
|
|
255
|
-
myapi container deploy <id> registry.example.com/my-app:v2
|
|
356
|
+
Examples:
|
|
357
|
+
myapi container deploy <id> registry.example.com/my-app:v2
|
|
358
|
+
myapi container deploy <id> --source ./my-app
|
|
359
|
+
myapi container deploy <id> --source ./context.tar.gz`,
|
|
256
360
|
'list': 'myapi container list [--org <id>] [--json]',
|
|
257
361
|
'get': 'myapi container get <id> [--org <id>] [--json]',
|
|
258
362
|
'logs': `myapi container logs <id> [--tail <n>] [--org <id>] [--json]
|
|
@@ -281,7 +385,7 @@ dependencies and long execution.
|
|
|
281
385
|
Subcommands:
|
|
282
386
|
create Register a container and get its scoped API key (returned once)
|
|
283
387
|
delete <id> Soft-delete and revoke its scoped API key
|
|
284
|
-
deploy <id> <image> Ship a pre-built image and go live
|
|
388
|
+
deploy <id> <image> Ship a pre-built image (or --source <dir|tar> to build) and go live
|
|
285
389
|
domain <id> <domain> Bind a custom domain (--remove to unbind)
|
|
286
390
|
get <id> Inspect a container
|
|
287
391
|
list List containers in your org
|
package/dist/commands/domain.js
CHANGED
|
@@ -299,7 +299,7 @@ export async function emailSetup(domainArg, flags) {
|
|
|
299
299
|
printJson(res);
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
|
-
success(`Email infra provisioning on ${res.
|
|
302
|
+
success(`Email infra provisioning on ${res.subdomain} (state: ${res.email_infra})`);
|
|
303
303
|
if (res.next_step)
|
|
304
304
|
info(res.next_step);
|
|
305
305
|
info(`Track with: myapi domain status ${domain} --watch`);
|
|
@@ -533,7 +533,7 @@ Prerequisites:
|
|
|
533
533
|
- WHOIS registrant info (ICANN requirement — see below)
|
|
534
534
|
|
|
535
535
|
Registrant (required at every register call):
|
|
536
|
-
Cheapest path: run \`myapi
|
|
536
|
+
Cheapest path: run \`myapi account registrant set\` once. The CLI stores
|
|
537
537
|
your name/email/phone/address and auto-injects it on every register.
|
|
538
538
|
|
|
539
539
|
Per-call override (agent-friendly):
|
|
@@ -10,7 +10,6 @@ import * as mailbox from './mailbox.js';
|
|
|
10
10
|
import * as message from './message.js';
|
|
11
11
|
import * as warmup from './warmup.js';
|
|
12
12
|
import * as template from './template.js';
|
|
13
|
-
import * as campaign from './campaign.js';
|
|
14
13
|
import * as verify from './verify.js';
|
|
15
14
|
export const SCHEMA = {
|
|
16
15
|
org: 'string',
|
|
@@ -53,15 +52,6 @@ const DEPRECATED_ALIASES = {
|
|
|
53
52
|
'edit-template': ['template', 'edit'],
|
|
54
53
|
'send-test': ['template', 'send-test'],
|
|
55
54
|
'delete-template': ['template', 'delete'],
|
|
56
|
-
'create-campaign': ['campaign', 'create'],
|
|
57
|
-
'list-campaigns': ['campaign', 'list'],
|
|
58
|
-
'get-campaign': ['campaign', 'get'],
|
|
59
|
-
'update-campaign': ['campaign', 'update'],
|
|
60
|
-
'upload-contacts': ['campaign', 'upload-contacts'],
|
|
61
|
-
'start-campaign': ['campaign', 'start'],
|
|
62
|
-
'pause-campaign': ['campaign', 'pause'],
|
|
63
|
-
'resume-campaign': ['campaign', 'resume'],
|
|
64
|
-
'campaign-stats': ['campaign', 'stats'],
|
|
65
55
|
};
|
|
66
56
|
async function dispatchNamespace(ns, sub, restArgs, flags) {
|
|
67
57
|
switch (ns) {
|
|
@@ -69,7 +59,6 @@ async function dispatchNamespace(ns, sub, restArgs, flags) {
|
|
|
69
59
|
case 'message': return message.run(sub, restArgs, flags);
|
|
70
60
|
case 'warmup': return warmup.run(sub, restArgs, flags);
|
|
71
61
|
case 'template': return template.run(sub, restArgs, flags);
|
|
72
|
-
case 'campaign': return campaign.run(sub, restArgs, flags);
|
|
73
62
|
case 'verify': return verify.run(sub, restArgs, flags);
|
|
74
63
|
default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
|
|
75
64
|
}
|
|
@@ -79,7 +68,6 @@ export async function run(subcommand, args, flags) {
|
|
|
79
68
|
info(`Usage: myapi email <namespace> <subcommand>
|
|
80
69
|
|
|
81
70
|
Namespaces:
|
|
82
|
-
campaign Email campaigns (org-scoped) — create, list, get, update, upload-contacts, start, pause, resume, stats
|
|
83
71
|
mailbox Mailboxes (account-scoped) — create, list, activate-sending
|
|
84
72
|
message Send & read mail (account-scoped) — send, status, sent, inbox, outbox, get
|
|
85
73
|
template Email templates (org-scoped) — generate, list, edit, send-test, delete
|
|
@@ -90,7 +78,6 @@ Examples:
|
|
|
90
78
|
myapi email mailbox create hello@example.com
|
|
91
79
|
myapi email message send --from a@x --to b@y --subject hi --body "hello"
|
|
92
80
|
myapi email template generate welcome --prompt "Welcome email"
|
|
93
|
-
myapi email campaign create "Q2 Launch" --template-id <id> --from a@x
|
|
94
81
|
myapi email verify alice@example.com
|
|
95
82
|
|
|
96
83
|
Org-scoped commands accept --org <id> (or set default: myapi config set-org <id>).
|
package/dist/commands/fn.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function list(flags: Flags): Promise<void>;
|
|
|
11
11
|
export declare function get(id: string, flags: Flags): Promise<void>;
|
|
12
12
|
export declare function del(id: string, flags: Flags): Promise<void>;
|
|
13
13
|
export declare function deploy(id: string, bundlePath: string, flags: Flags): Promise<void>;
|
|
14
|
+
export declare function _parseSetPairs(raw: string | string[]): Record<string, string> | string;
|
|
14
15
|
export declare function setEnv(id: string, name: string, value: string, flags: Flags): Promise<void>;
|
|
15
16
|
export declare function runs(id: string, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|