@myapihq/cli 1.2.9 → 1.3.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/container.d.ts +1 -0
- package/dist/commands/container.js +41 -0
- package/dist/commands/email/mailbox.js +28 -1
- package/dist/commands/queue-validation.test.d.ts +1 -0
- package/dist/commands/queue-validation.test.js +38 -0
- package/dist/commands/queue.d.ts +14 -0
- package/dist/commands/queue.js +215 -0
- package/dist/commands/task-validation.test.d.ts +1 -0
- package/dist/commands/task-validation.test.js +37 -0
- package/dist/commands/task.d.ts +18 -0
- package/dist/commands/task.js +288 -0
- package/dist/commands/workflow-validation.test.js +27 -0
- package/dist/commands/workflow.js +17 -1
- package/dist/completion.js +5 -3
- package/dist/exposes.test.js +2 -0
- package/dist/index.js +14 -0
- package/dist/sdk-container.test.js +35 -1
- package/dist/sdk-email-forwarding.test.d.ts +1 -0
- package/dist/sdk-email-forwarding.test.js +48 -0
- package/dist/sdk-queue.test.d.ts +1 -0
- package/dist/sdk-queue.test.js +86 -0
- package/dist/sdk-task.test.d.ts +1 -0
- package/dist/sdk-task.test.js +110 -0
- package/dist/skills/my-domain-api/SKILL.md +2 -0
- package/dist/skills/my-workflow-api/SKILL.md +10 -1
- package/package.json +2 -2
|
@@ -13,4 +13,5 @@ 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
15
|
export declare function logs(id: string, flags: Flags): Promise<void>;
|
|
16
|
+
export declare function domain(id: string, domainArg: string | undefined, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -10,6 +10,8 @@ export const EXPOSES = [
|
|
|
10
10
|
'DELETE /container/orgs/{org_id}/containers/{id}',
|
|
11
11
|
'POST /container/orgs/{org_id}/containers/{id}/deploy',
|
|
12
12
|
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
13
|
+
'POST /container/orgs/{org_id}/containers/{id}/domain',
|
|
14
|
+
'DELETE /container/orgs/{org_id}/containers/{id}/domain',
|
|
13
15
|
];
|
|
14
16
|
export const SCHEMA = {
|
|
15
17
|
name: 'string',
|
|
@@ -22,6 +24,7 @@ export const SCHEMA = {
|
|
|
22
24
|
port: 'number',
|
|
23
25
|
env: 'string',
|
|
24
26
|
tail: 'number',
|
|
27
|
+
remove: 'boolean',
|
|
25
28
|
};
|
|
26
29
|
const CONTAINER_TYPES = ['service', 'worker', 'job'];
|
|
27
30
|
// Mirrors validateName in myapi-hq/internal/routes/container/crud.go —
|
|
@@ -146,6 +149,8 @@ export async function get(id, flags) {
|
|
|
146
149
|
if (c.port)
|
|
147
150
|
info(`Port: ${c.port}`);
|
|
148
151
|
info(`URL: ${c.url || '(not deployed)'}`);
|
|
152
|
+
if (c.custom_domain)
|
|
153
|
+
info(`Custom domain: ${c.custom_domain}`);
|
|
149
154
|
info(`Created: ${c.created_at}`);
|
|
150
155
|
info(`Updated: ${c.updated_at}`);
|
|
151
156
|
}
|
|
@@ -198,6 +203,31 @@ export async function logs(id, flags) {
|
|
|
198
203
|
info(`${e.timestamp} ${(e.severity || '').padEnd(8)} ${e.text}`);
|
|
199
204
|
}
|
|
200
205
|
}
|
|
206
|
+
// domain binds (or, with --remove, unbinds) a custom domain on a deployed
|
|
207
|
+
// container. The parent domain must be MyAPI-managed.
|
|
208
|
+
export async function domain(id, domainArg, flags) {
|
|
209
|
+
const config = requireConfig();
|
|
210
|
+
const orgId = requireOrg(flags, config, 'myapi container domain <id> <domain> [--org <id>]');
|
|
211
|
+
if (!id)
|
|
212
|
+
error('Missing id.\nUsage: myapi container domain <id> <domain> (or --remove to unbind)');
|
|
213
|
+
if (flags.remove) {
|
|
214
|
+
await sdkContainer.unbindDomain(config.api_key, orgId, id);
|
|
215
|
+
success(`Removed custom domain from container ${id}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!domainArg) {
|
|
219
|
+
error('Missing <domain>.\nUsage: myapi container domain <id> <domain>\n or: myapi container domain <id> --remove');
|
|
220
|
+
}
|
|
221
|
+
const binding = await sdkContainer.bindDomain(config.api_key, orgId, id, domainArg);
|
|
222
|
+
if (flags.json) {
|
|
223
|
+
printJson(binding);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
success(`Custom domain bound: ${binding.custom_domain}`);
|
|
227
|
+
info(`Container: ${binding.container_id}`);
|
|
228
|
+
info(`Origin: ${binding.origin}`);
|
|
229
|
+
info(`Status: ${binding.status}`);
|
|
230
|
+
}
|
|
201
231
|
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
202
232
|
const SUBCOMMAND_USAGE = {
|
|
203
233
|
'create': `myapi container create --name <name> [--type service|worker|job] [--cron <expr>]
|
|
@@ -229,6 +259,15 @@ Example:
|
|
|
229
259
|
|
|
230
260
|
Recent Cloud Run runtime logs, newest first. --tail caps the count
|
|
231
261
|
(default 100, max 1000).`,
|
|
262
|
+
'domain': `myapi container domain <id> <domain> [--org <id>] [--json]
|
|
263
|
+
myapi container domain <id> --remove [--org <id>]
|
|
264
|
+
|
|
265
|
+
Binds a custom domain to a deployed container, served over HTTPS via
|
|
266
|
+
Cloudflare. The domain's MyAPI-managed parent domain must already be
|
|
267
|
+
registered. --remove unbinds it.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
myapi container domain <id> app.synthesisdaily.com`,
|
|
232
271
|
'delete': 'myapi container delete <id> [--org <id>]',
|
|
233
272
|
};
|
|
234
273
|
export async function run(subcommand, args, flags) {
|
|
@@ -245,6 +284,7 @@ Subcommands:
|
|
|
245
284
|
list List containers in your org
|
|
246
285
|
get <id> Inspect a container
|
|
247
286
|
logs <id> Show recent runtime logs (--tail <n>)
|
|
287
|
+
domain <id> <domain> Bind a custom domain (--remove to unbind)
|
|
248
288
|
delete <id> Soft-delete and revoke its scoped API key
|
|
249
289
|
|
|
250
290
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
@@ -264,6 +304,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
|
264
304
|
case 'list': return list(flags);
|
|
265
305
|
case 'get': return get(args[0], flags);
|
|
266
306
|
case 'logs': return logs(args[0], flags);
|
|
307
|
+
case 'domain': return domain(args[0], args[1], flags);
|
|
267
308
|
case 'delete': return del(args[0], flags);
|
|
268
309
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi container --help" for a list of valid subcommands.`);
|
|
269
310
|
}
|
|
@@ -4,6 +4,8 @@ import { success, error, printTable, info } from '../../output.js';
|
|
|
4
4
|
export const EXPOSES = [
|
|
5
5
|
'POST /email/mailboxes/create',
|
|
6
6
|
'GET /email/mailboxes',
|
|
7
|
+
'PUT /email/mailboxes/{address}/forwarding',
|
|
8
|
+
'DELETE /email/mailboxes/{address}/forwarding',
|
|
7
9
|
'POST /email/sending/activate',
|
|
8
10
|
];
|
|
9
11
|
async function create(addressArg, flags) {
|
|
@@ -52,6 +54,22 @@ async function activateSending(flags) {
|
|
|
52
54
|
const res = await sdkEmail.activateSending(config.api_key, address);
|
|
53
55
|
success(`Sending activated: ${address} (${res.emails_quota_remaining} emails/day quota)`);
|
|
54
56
|
}
|
|
57
|
+
async function setForwarding(address, forwardTo, _flags) {
|
|
58
|
+
const config = requireConfig();
|
|
59
|
+
if (!address || !forwardTo) {
|
|
60
|
+
error('Missing required arguments.\nUsage: myapi email mailbox set-forwarding <user@domain> <forward-to@domain>');
|
|
61
|
+
}
|
|
62
|
+
const res = await sdkEmail.setForwarding(config.api_key, address, forwardTo);
|
|
63
|
+
success(`Forwarding set: ${res.address} → ${res.forward_to}`);
|
|
64
|
+
info('A copy of every incoming message is redirected; the original is kept in the mailbox.');
|
|
65
|
+
}
|
|
66
|
+
async function clearForwarding(address, _flags) {
|
|
67
|
+
const config = requireConfig();
|
|
68
|
+
if (!address)
|
|
69
|
+
error('Missing required arguments.\nUsage: myapi email mailbox clear-forwarding <user@domain>');
|
|
70
|
+
await sdkEmail.deleteForwarding(config.api_key, address);
|
|
71
|
+
success(`Forwarding cleared for ${address}`);
|
|
72
|
+
}
|
|
55
73
|
const USAGE = {
|
|
56
74
|
'create': `myapi email mailbox create <user@domain> [--display-name <name>]
|
|
57
75
|
myapi email mailbox create --username <u> --domain <d> [--display-name <name>]
|
|
@@ -65,6 +83,11 @@ matches the rest of the CLI ("first required arg is positional").`,
|
|
|
65
83
|
org (orphaned mailboxes — domain was unassigned
|
|
66
84
|
without first deleting them).`,
|
|
67
85
|
'activate-sending': 'myapi email mailbox activate-sending --address <email>',
|
|
86
|
+
'set-forwarding': `myapi email mailbox set-forwarding <user@domain> <forward-to@domain>
|
|
87
|
+
|
|
88
|
+
Redirects a copy of every incoming message to an external address
|
|
89
|
+
(server-side). The original is kept in the mailbox.`,
|
|
90
|
+
'clear-forwarding': 'myapi email mailbox clear-forwarding <user@domain>',
|
|
68
91
|
};
|
|
69
92
|
export async function run(sub, args, flags) {
|
|
70
93
|
if (!sub || (flags.help && !sub)) {
|
|
@@ -73,7 +96,9 @@ export async function run(sub, args, flags) {
|
|
|
73
96
|
Subcommands:
|
|
74
97
|
create Create a mailbox (positional <user@domain> or --username/--domain)
|
|
75
98
|
list List mailboxes on a domain (--domain) or orphaned ones (--filter unassigned)
|
|
76
|
-
activate-sending Activate outbound sending for a mailbox
|
|
99
|
+
activate-sending Activate outbound sending for a mailbox
|
|
100
|
+
set-forwarding Forward a copy of incoming mail to an external address
|
|
101
|
+
clear-forwarding Stop forwarding for a mailbox`);
|
|
77
102
|
return;
|
|
78
103
|
}
|
|
79
104
|
if (flags.help) {
|
|
@@ -88,6 +113,8 @@ Subcommands:
|
|
|
88
113
|
case 'create': return create(args[0], flags);
|
|
89
114
|
case 'list': return list(flags);
|
|
90
115
|
case 'activate-sending': return activateSending(flags);
|
|
116
|
+
case 'set-forwarding': return setForwarding(args[0], args[1], flags);
|
|
117
|
+
case 'clear-forwarding': return clearForwarding(args[0], flags);
|
|
91
118
|
default: error(`Unknown subcommand: ${sub}. Run "myapi email mailbox --help" for the list.`);
|
|
92
119
|
}
|
|
93
120
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Unit tests for the queue CLI's pure validators.
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { _validateConsumerUrl, _parseDependsOn } from './queue.js';
|
|
4
|
+
describe('_validateConsumerUrl', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
'https://example.com/consume',
|
|
7
|
+
'http://example.com/consume',
|
|
8
|
+
'https://fn.myapi.com/org/fn_123',
|
|
9
|
+
'https://example.com:8443/path?q=v',
|
|
10
|
+
])('accepts %s', (url) => {
|
|
11
|
+
expect(_validateConsumerUrl(url)).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
it.each([
|
|
14
|
+
['ftp://example.com', /http:\/\/ or https:\/\//],
|
|
15
|
+
['javascript:alert(1)', /http:\/\/ or https:\/\//],
|
|
16
|
+
['file:///etc/passwd', /http:\/\/ or https:\/\//],
|
|
17
|
+
])('rejects non-http(s) %s', (url, re) => {
|
|
18
|
+
expect(_validateConsumerUrl(url)).toMatch(re);
|
|
19
|
+
});
|
|
20
|
+
it.each(['not a url', 'just-text', 'http//missing-colon'])('rejects malformed %s', (url) => {
|
|
21
|
+
expect(_validateConsumerUrl(url)).toMatch(/not a valid URL/);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('_parseDependsOn', () => {
|
|
25
|
+
it('returns undefined for empty / non-string input', () => {
|
|
26
|
+
expect(_parseDependsOn(undefined)).toBeUndefined();
|
|
27
|
+
expect(_parseDependsOn('')).toBeUndefined();
|
|
28
|
+
expect(_parseDependsOn(' ')).toBeUndefined();
|
|
29
|
+
expect(_parseDependsOn(true)).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
it('splits a comma-separated list and trims', () => {
|
|
32
|
+
expect(_parseDependsOn('j1,j2,j3')).toEqual(['j1', 'j2', 'j3']);
|
|
33
|
+
expect(_parseDependsOn(' j1 , j2 ')).toEqual(['j1', 'j2']);
|
|
34
|
+
});
|
|
35
|
+
it('drops empty segments', () => {
|
|
36
|
+
expect(_parseDependsOn('j1,,j2,')).toEqual(['j1', 'j2']);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
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 _validateConsumerUrl(url: string): string | null;
|
|
7
|
+
export declare function _parseDependsOn(raw: unknown): string[] | undefined;
|
|
8
|
+
export declare function create(name: string, flags: Flags): Promise<void>;
|
|
9
|
+
export declare function list(flags: Flags): Promise<void>;
|
|
10
|
+
export declare function get(name: string, flags: Flags): Promise<void>;
|
|
11
|
+
export declare function enqueue(name: string, flags: Flags): Promise<void>;
|
|
12
|
+
export declare function jobs(name: string, flags: Flags): Promise<void>;
|
|
13
|
+
export declare function job(jobId: string, flags: Flags): Promise<void>;
|
|
14
|
+
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { queue as sdkQueue } 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 /queue/orgs/{org_id}/queues',
|
|
8
|
+
'GET /queue/orgs/{org_id}/queues',
|
|
9
|
+
'GET /queue/orgs/{org_id}/queues/{name}',
|
|
10
|
+
'POST /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
11
|
+
'GET /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
12
|
+
'GET /queue/orgs/{org_id}/jobs/{id}',
|
|
13
|
+
];
|
|
14
|
+
export const SCHEMA = {
|
|
15
|
+
name: 'string',
|
|
16
|
+
'consumer-url': 'string',
|
|
17
|
+
'max-attempts': 'number',
|
|
18
|
+
'max-concurrency': 'number',
|
|
19
|
+
payload: 'string',
|
|
20
|
+
'dedup-key': 'string',
|
|
21
|
+
delay: 'number',
|
|
22
|
+
'depends-on': 'string',
|
|
23
|
+
status: 'string',
|
|
24
|
+
limit: 'number',
|
|
25
|
+
};
|
|
26
|
+
// Pure validator — returns an error message or null. Mirrors
|
|
27
|
+
// webhook.ts's _validateForwardUrl so it's unit-testable.
|
|
28
|
+
export function _validateConsumerUrl(url) {
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = new URL(url);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return `consumer URL "${url}" is not a valid URL.`;
|
|
35
|
+
}
|
|
36
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
37
|
+
return `consumer URL must use http:// or https:// — got "${url}".`;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// Parses a comma-separated --depends-on flag into a clean id array.
|
|
42
|
+
export function _parseDependsOn(raw) {
|
|
43
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
44
|
+
return undefined;
|
|
45
|
+
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
// ── Queues ───────────────────────────────────────────────────────────────────
|
|
48
|
+
export async function create(name, flags) {
|
|
49
|
+
const config = requireConfig();
|
|
50
|
+
const orgId = requireOrg(flags, config, 'myapi queue create <name> --consumer-url <url> [--org <id>]');
|
|
51
|
+
const queueName = name || flags.name;
|
|
52
|
+
requireArg(queueName, 'name', 'myapi queue create <name> --consumer-url <url>');
|
|
53
|
+
const consumerUrl = flags['consumer-url'];
|
|
54
|
+
if (typeof consumerUrl !== 'string' || consumerUrl === '') {
|
|
55
|
+
error('Missing --consumer-url.\nUsage: myapi queue create <name> --consumer-url <url> [--max-attempts <n>] [--max-concurrency <n>]');
|
|
56
|
+
}
|
|
57
|
+
const urlErr = _validateConsumerUrl(consumerUrl);
|
|
58
|
+
if (urlErr)
|
|
59
|
+
error(urlErr);
|
|
60
|
+
const q = await sdkQueue.createQueue(config.api_key, orgId, {
|
|
61
|
+
name: queueName,
|
|
62
|
+
consumerUrl,
|
|
63
|
+
maxAttempts: typeof flags['max-attempts'] === 'number' ? flags['max-attempts'] : undefined,
|
|
64
|
+
maxConcurrency: typeof flags['max-concurrency'] === 'number' ? flags['max-concurrency'] : undefined,
|
|
65
|
+
});
|
|
66
|
+
if (flags.json) {
|
|
67
|
+
printJson(q);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
success(`Queue created: ${q.name}`);
|
|
71
|
+
info(`Consumer: ${q.consumer_url}`);
|
|
72
|
+
info(`Max attempts: ${q.max_attempts}`);
|
|
73
|
+
info(`Max concurrency: ${q.max_concurrency}`);
|
|
74
|
+
}
|
|
75
|
+
export async function list(flags) {
|
|
76
|
+
const config = requireConfig();
|
|
77
|
+
const orgId = requireOrg(flags, config, 'myapi queue list [--org <id>]');
|
|
78
|
+
const queues = await sdkQueue.listQueues(config.api_key, orgId);
|
|
79
|
+
if (flags.json) {
|
|
80
|
+
printJson(queues);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
printTable(queues.map(q => ({ name: q.name })), {
|
|
84
|
+
flags,
|
|
85
|
+
empty: 'No queues yet. Create one with: myapi queue create <name> --consumer-url <url>',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function get(name, flags) {
|
|
89
|
+
const config = requireConfig();
|
|
90
|
+
const orgId = requireOrg(flags, config, 'myapi queue get <name> [--org <id>]');
|
|
91
|
+
requireArg(name, 'name', 'myapi queue get <name>');
|
|
92
|
+
const q = await sdkQueue.getQueue(config.api_key, orgId, name);
|
|
93
|
+
if (flags.json) {
|
|
94
|
+
printJson(q);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
info(`Name: ${q.name}`);
|
|
98
|
+
info(`Consumer: ${q.consumer_url}`);
|
|
99
|
+
info(`Max attempts: ${q.max_attempts}`);
|
|
100
|
+
info(`Max concurrency: ${q.max_concurrency}`);
|
|
101
|
+
}
|
|
102
|
+
// ── Jobs ─────────────────────────────────────────────────────────────────────
|
|
103
|
+
export async function enqueue(name, flags) {
|
|
104
|
+
const config = requireConfig();
|
|
105
|
+
const orgId = requireOrg(flags, config, 'myapi queue enqueue <name> --payload <json> [--org <id>]');
|
|
106
|
+
requireArg(name, 'name', 'myapi queue enqueue <name> --payload <json>');
|
|
107
|
+
let payload;
|
|
108
|
+
if (typeof flags.payload === 'string') {
|
|
109
|
+
try {
|
|
110
|
+
payload = JSON.parse(flags.payload);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
error(`--payload is not valid JSON: ${e?.message ?? e}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (flags.payload === true) {
|
|
117
|
+
error('--payload needs a JSON value, e.g. --payload \'{"order_id":42}\'');
|
|
118
|
+
}
|
|
119
|
+
const job = await sdkQueue.enqueueJob(config.api_key, orgId, name, {
|
|
120
|
+
payload,
|
|
121
|
+
dedupKey: typeof flags['dedup-key'] === 'string' ? flags['dedup-key'] : undefined,
|
|
122
|
+
delaySeconds: typeof flags.delay === 'number' ? flags.delay : undefined,
|
|
123
|
+
dependsOn: _parseDependsOn(flags['depends-on']),
|
|
124
|
+
});
|
|
125
|
+
if (flags.json) {
|
|
126
|
+
printJson(job);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
success(`Job enqueued: ${job.id}`);
|
|
130
|
+
info(`Status: ${job.status}`);
|
|
131
|
+
}
|
|
132
|
+
export async function jobs(name, flags) {
|
|
133
|
+
const config = requireConfig();
|
|
134
|
+
const orgId = requireOrg(flags, config, 'myapi queue jobs <name> [--status <s>] [--limit <n>] [--org <id>]');
|
|
135
|
+
requireArg(name, 'name', 'myapi queue jobs <name>');
|
|
136
|
+
const list = await sdkQueue.listJobs(config.api_key, orgId, name, {
|
|
137
|
+
status: typeof flags.status === 'string' ? flags.status : undefined,
|
|
138
|
+
limit: typeof flags.limit === 'number' ? flags.limit : undefined,
|
|
139
|
+
});
|
|
140
|
+
if (flags.json) {
|
|
141
|
+
printJson(list);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
printTable(list.map(j => ({
|
|
145
|
+
id: j.id,
|
|
146
|
+
status: j.status,
|
|
147
|
+
attempt: j.attempt,
|
|
148
|
+
created: j.created_at ? formatDate(j.created_at) : '',
|
|
149
|
+
})), { flags, empty: 'No jobs in this queue.' });
|
|
150
|
+
}
|
|
151
|
+
export async function job(jobId, flags) {
|
|
152
|
+
const config = requireConfig();
|
|
153
|
+
const orgId = requireOrg(flags, config, 'myapi queue job <job_id> [--org <id>]');
|
|
154
|
+
requireArg(jobId, 'job_id', 'myapi queue job <job_id>');
|
|
155
|
+
const j = await sdkQueue.getJob(config.api_key, orgId, jobId);
|
|
156
|
+
if (flags.json) {
|
|
157
|
+
printJson(j);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
info(`Job: ${j.id}`);
|
|
161
|
+
info(`Queue: ${j.queue_id}`);
|
|
162
|
+
info(`Status: ${j.status}`);
|
|
163
|
+
info(`Attempt: ${j.attempt} / ${j.max_attempts}`);
|
|
164
|
+
if (j.last_error)
|
|
165
|
+
info(`Error: ${j.last_error}`);
|
|
166
|
+
}
|
|
167
|
+
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
168
|
+
const SUBCOMMAND_USAGE = {
|
|
169
|
+
'create': 'myapi queue create <name> --consumer-url <url> [--max-attempts <n>] [--max-concurrency <n>] [--org <id>]',
|
|
170
|
+
'list': 'myapi queue list [--org <id>] [--json]',
|
|
171
|
+
'get': 'myapi queue get <name> [--org <id>] [--json]',
|
|
172
|
+
'enqueue': 'myapi queue enqueue <name> --payload <json> [--dedup-key <k>] [--delay <seconds>] [--depends-on <id,id>] [--org <id>]',
|
|
173
|
+
'jobs': 'myapi queue jobs <name> [--status <s>] [--limit <n>] [--org <id>] [--json]',
|
|
174
|
+
'job': 'myapi queue job <job_id> [--org <id>] [--json]',
|
|
175
|
+
};
|
|
176
|
+
export async function run(subcommand, args, flags) {
|
|
177
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
178
|
+
info(`Usage: myapi queue <subcommand>
|
|
179
|
+
|
|
180
|
+
A durable HTTP-consumer job queue — jobs are POSTed to the queue's
|
|
181
|
+
consumer_url with retry/backoff, a concurrency cap, and a dependency DAG.
|
|
182
|
+
|
|
183
|
+
Queues:
|
|
184
|
+
create <name> Create a queue (--consumer-url, --max-attempts, --max-concurrency)
|
|
185
|
+
list List queues
|
|
186
|
+
get <name> Show a queue's policy
|
|
187
|
+
|
|
188
|
+
Jobs:
|
|
189
|
+
enqueue <name> Enqueue a job (--payload, --dedup-key, --delay, --depends-on)
|
|
190
|
+
jobs <name> List a queue's jobs (--status, --limit)
|
|
191
|
+
job <job_id> Show a single job's status
|
|
192
|
+
|
|
193
|
+
For "needs a decision" work (agent/human), see: myapi task --help
|
|
194
|
+
|
|
195
|
+
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (flags.help) {
|
|
199
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
200
|
+
if (usage)
|
|
201
|
+
info(`Usage: ${usage}`);
|
|
202
|
+
else
|
|
203
|
+
info(`Unknown subcommand: ${subcommand}. Run "myapi queue --help" for the list.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
switch (subcommand) {
|
|
207
|
+
case 'create': return create(args[0], flags);
|
|
208
|
+
case 'list': return list(flags);
|
|
209
|
+
case 'get': return get(args[0], flags);
|
|
210
|
+
case 'enqueue': return enqueue(args[0], flags);
|
|
211
|
+
case 'jobs': return jobs(args[0], flags);
|
|
212
|
+
case 'job': return job(args[0], flags);
|
|
213
|
+
default: error(`Unknown subcommand: ${subcommand}. Run "myapi queue --help" for a list of valid subcommands.`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Unit tests for the task CLI's pure parsers.
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { _parseResolveOn, _splitList } from './task.js';
|
|
4
|
+
describe('_parseResolveOn', () => {
|
|
5
|
+
it('parses a bare event type', () => {
|
|
6
|
+
expect(_parseResolveOn('payment.succeeded')).toEqual({ event_type: 'payment.succeeded' });
|
|
7
|
+
});
|
|
8
|
+
it('parses event_type:field=value', () => {
|
|
9
|
+
expect(_parseResolveOn('payment.succeeded:order_id=o_42')).toEqual({
|
|
10
|
+
event_type: 'payment.succeeded', field: 'order_id', value: 'o_42',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('keeps = signs inside the value', () => {
|
|
14
|
+
expect(_parseResolveOn('evt:token=a=b=c')).toEqual({
|
|
15
|
+
event_type: 'evt', field: 'token', value: 'a=b=c',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('rejects an empty string', () => {
|
|
19
|
+
expect(_parseResolveOn(' ')).toMatch(/cannot be empty/);
|
|
20
|
+
});
|
|
21
|
+
it('rejects a leading colon (no event type)', () => {
|
|
22
|
+
expect(_parseResolveOn(':field=value')).toMatch(/must start with an event type/);
|
|
23
|
+
});
|
|
24
|
+
it('rejects a field match without =', () => {
|
|
25
|
+
expect(_parseResolveOn('evt:justfield')).toMatch(/field=value/);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('_splitList', () => {
|
|
29
|
+
it('returns undefined for empty / non-string input', () => {
|
|
30
|
+
expect(_splitList(undefined)).toBeUndefined();
|
|
31
|
+
expect(_splitList('')).toBeUndefined();
|
|
32
|
+
expect(_splitList(true)).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
it('splits and trims, dropping empties', () => {
|
|
35
|
+
expect(_splitList('support, sales ,, billing')).toEqual(['support', 'sales', 'billing']);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { task as sdkTask } from '@myapihq/sdk';
|
|
2
|
+
import type { FlagSchema } from '../flags.js';
|
|
3
|
+
import { type Flags } from '../helpers.js';
|
|
4
|
+
import type { Exposes } from '../exposes.js';
|
|
5
|
+
export declare const EXPOSES: Exposes;
|
|
6
|
+
export declare const SCHEMA: FlagSchema;
|
|
7
|
+
export declare const TASK_IMPORTANCE: readonly ["low", "normal", "high", "critical"];
|
|
8
|
+
export declare function _parseResolveOn(raw: string): sdkTask.ResolveOn | string;
|
|
9
|
+
export declare function _splitList(raw: unknown): string[] | undefined;
|
|
10
|
+
export declare function create(description: string, flags: Flags): Promise<void>;
|
|
11
|
+
export declare function list(flags: Flags): Promise<void>;
|
|
12
|
+
export declare function get(id: string, flags: Flags): Promise<void>;
|
|
13
|
+
export declare function claim(id: string, flags: Flags): Promise<void>;
|
|
14
|
+
export declare function extend(id: string, flags: Flags): Promise<void>;
|
|
15
|
+
export declare function resolve(id: string, flags: Flags): Promise<void>;
|
|
16
|
+
export declare function fail(id: string, flags: Flags): Promise<void>;
|
|
17
|
+
export declare function cancel(id: string, flags: Flags): Promise<void>;
|
|
18
|
+
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|