@myapihq/cli 1.0.84 → 1.1.0-wip.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/auth.d.ts +8 -3
- package/dist/commands/auth.js +84 -60
- package/dist/commands/billing.d.ts +8 -4
- package/dist/commands/billing.js +46 -27
- package/dist/commands/config.d.ts +8 -5
- package/dist/commands/config.js +52 -27
- package/dist/commands/domain.d.ts +12 -9
- package/dist/commands/domain.js +124 -86
- package/dist/commands/email/campaign.d.ts +2 -0
- package/dist/commands/email/campaign.js +152 -0
- package/dist/commands/email/index.d.ts +4 -0
- package/dist/commands/email/index.js +98 -0
- package/dist/commands/email/mailbox.d.ts +2 -0
- package/dist/commands/email/mailbox.js +88 -0
- package/dist/commands/email/message.d.ts +2 -0
- package/dist/commands/email/message.js +115 -0
- package/dist/commands/email/template.d.ts +2 -0
- package/dist/commands/email/template.js +106 -0
- package/dist/commands/email/warmup.d.ts +2 -0
- package/dist/commands/email/warmup.js +43 -0
- package/dist/commands/email.d.ts +4 -12
- package/dist/commands/email.js +528 -146
- package/dist/commands/funnel.d.ts +10 -7
- package/dist/commands/funnel.js +79 -55
- package/dist/commands/image.js +25 -15
- package/dist/commands/keys.d.ts +8 -3
- package/dist/commands/keys.js +71 -35
- package/dist/commands/org.d.ts +9 -5
- package/dist/commands/org.js +100 -64
- package/dist/commands/pixel.js +23 -11
- package/dist/commands/setup.d.ts +3 -2
- package/dist/commands/setup.js +160 -165
- package/dist/commands/storage.js +25 -15
- package/dist/commands/update.d.ts +2 -1
- package/dist/commands/update.js +5 -0
- package/dist/commands/url.js +19 -7
- package/dist/commands/webhook.d.ts +8 -5
- package/dist/commands/webhook.js +70 -38
- package/dist/commands/workflow.d.ts +13 -7
- package/dist/commands/workflow.js +179 -58
- package/dist/config.js +10 -5
- package/dist/flags.d.ts +8 -0
- package/dist/flags.js +88 -0
- package/dist/flags.test.d.ts +1 -0
- package/dist/flags.test.js +73 -0
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +31 -0
- package/dist/index.js +98 -109
- package/dist/output.d.ts +12 -1
- package/dist/output.js +16 -6
- package/dist/prompt.d.ts +24 -0
- package/dist/prompt.js +41 -0
- package/dist/skills/my-email-api/README.md +45 -0
- package/dist/skills/my-email-api/SKILL.md +104 -0
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-email-api/make/.gitkeep +0 -0
- package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/README.md +40 -0
- package/dist/skills/my-webhook-api/SKILL.md +138 -0
- package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/README.md +36 -0
- package/dist/skills/my-workflow-api/SKILL.md +156 -0
- package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
- package/dist/utils.d.ts +26 -4
- package/dist/utils.js +32 -33
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +48 -0
- package/package.json +9 -4
- package/dist/commands/account.d.ts +0 -4
- package/dist/commands/account.js +0 -80
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-email-api
|
|
3
|
+
description: >
|
|
4
|
+
Send transactional and bulk email from your own domain. Create mailboxes, send/receive messages, generate AI templates, run drip campaigns, and manage warmup.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyEmailAPI
|
|
8
|
+
|
|
9
|
+
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 (one campaign belongs to one org).
|
|
10
|
+
|
|
11
|
+
## How It Fits Together
|
|
12
|
+
|
|
13
|
+
- Requires `api_key` from **myapihq** and a registered domain via **mydomainapi**.
|
|
14
|
+
- Mailboxes live on a domain (e.g. `hello@yourdomain.com`).
|
|
15
|
+
- Sending must be activated per-mailbox (`mailbox activate-sending`) before transactional sends or campaigns.
|
|
16
|
+
- Templates and campaigns require `org_id` (set a default with `myapi auth config set-org`).
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Create a mailbox on your registered domain
|
|
22
|
+
myapi email mailbox create hello@yourdomain.com --display-name "Hello"
|
|
23
|
+
|
|
24
|
+
# 2. Activate sending (required before send/campaign)
|
|
25
|
+
myapi email mailbox activate-sending --address hello@yourdomain.com
|
|
26
|
+
|
|
27
|
+
# 3. Send a transactional email
|
|
28
|
+
myapi email message send \
|
|
29
|
+
--from hello@yourdomain.com \
|
|
30
|
+
--to recipient@example.com \
|
|
31
|
+
--subject "Hi" \
|
|
32
|
+
--body "Test message"
|
|
33
|
+
|
|
34
|
+
# 4. Read inbox / outbox
|
|
35
|
+
myapi email message inbox hello@yourdomain.com
|
|
36
|
+
myapi email message outbox hello@yourdomain.com
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Command Map
|
|
40
|
+
|
|
41
|
+
| Namespace | Subcommands | Purpose |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `email mailbox` | `create`, `list`, `activate-sending` | Create and manage mailboxes on your domains |
|
|
44
|
+
| `email message` | `send`, `status`, `sent`, `inbox`, `outbox`, `get` | Transactional send + read |
|
|
45
|
+
| `email warmup` | `start`, `stats`, `pause`, `resume`, `stop` | IP/domain warmup for sending reputation |
|
|
46
|
+
| `email template` | `generate`, `list`, `edit`, `send-test`, `delete` | AI-generated HTML templates (org-scoped) |
|
|
47
|
+
| `email campaign` | `create`, `list`, `get`, `update`, `upload-contacts`, `start`, `pause`, `resume`, `stats` | Drip campaigns (org-scoped) |
|
|
48
|
+
|
|
49
|
+
## Templates (AI-generated)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Generate a template — polls the LLM job (up to 90s)
|
|
53
|
+
myapi email template generate welcome-v1 \
|
|
54
|
+
--prompt "A welcome email with our brand colors and a CTA to /onboarding"
|
|
55
|
+
|
|
56
|
+
# List, edit, delete
|
|
57
|
+
myapi email template list
|
|
58
|
+
myapi email template edit <id> --prompt "Make the CTA larger and red"
|
|
59
|
+
myapi email template send-test <id> --to me@yourdomain.com
|
|
60
|
+
myapi email template delete <id>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Campaigns
|
|
64
|
+
|
|
65
|
+
Campaigns send a template to a contact list at a per-day rate.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. Create a campaign linked to an existing template + sender mailbox
|
|
69
|
+
myapi email campaign create "Welcome series" \
|
|
70
|
+
--template-id <template_id> \
|
|
71
|
+
--from hello@yourdomain.com \
|
|
72
|
+
--per-day 50
|
|
73
|
+
|
|
74
|
+
# 2. Upload contacts
|
|
75
|
+
myapi email campaign upload-contacts <campaign_id> \
|
|
76
|
+
--emails "alice@example.com,bob@example.com"
|
|
77
|
+
|
|
78
|
+
# 3. Start (only after upload-contacts has finished processing)
|
|
79
|
+
myapi email campaign start <campaign_id>
|
|
80
|
+
|
|
81
|
+
# 4. Monitor
|
|
82
|
+
myapi email campaign stats <campaign_id>
|
|
83
|
+
myapi email campaign pause <campaign_id>
|
|
84
|
+
myapi email campaign resume <campaign_id>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Warmup
|
|
88
|
+
|
|
89
|
+
Warmup gradually ramps mailbox sending to build reputation with inbox providers. Recommended before high-volume campaigns.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
myapi email warmup start --address hello@yourdomain.com
|
|
93
|
+
myapi email warmup stats --address hello@yourdomain.com
|
|
94
|
+
myapi email warmup pause --address hello@yourdomain.com
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Notes
|
|
98
|
+
|
|
99
|
+
- A single mailbox is uniquely identified by its address (`username@domain`).
|
|
100
|
+
- Sending must be activated explicitly — newly-created mailboxes can receive but not send.
|
|
101
|
+
- Templates and campaigns are org-scoped. Set a default org once: `myapi auth config set-org <id>`.
|
|
102
|
+
- Domain assignment to org is enforced at unassign time: an active or paused campaign sending from a mailbox on the domain will block `domain unassign` with `DOMAIN_IN_USE`.
|
|
103
|
+
|
|
104
|
+
Run `myapi email --help` or `myapi email <namespace> --help` for full flag reference.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
# my-webhook-api
|
|
3
|
+
|
|
4
|
+
Inbound HTTP endpoints that receive POSTs from third-party services and store every delivery for inspection. Pair with my-workflow-api to react to incoming events.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- Create per-org webhook endpoints with unique inbound URLs
|
|
9
|
+
- Store every inbound delivery (body + headers)
|
|
10
|
+
- Inspect individual deliveries by id
|
|
11
|
+
- Bind webhooks to workflows for event-driven actions
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Create an endpoint
|
|
17
|
+
myapi webhook create "stripe-events"
|
|
18
|
+
# → prints: Inbound URL: https://api.myapihq.com/webhook/in/<slug>
|
|
19
|
+
|
|
20
|
+
# POST to it from anywhere
|
|
21
|
+
curl -X POST <inbound-url> -d '{"event":"x"}'
|
|
22
|
+
|
|
23
|
+
# List + inspect
|
|
24
|
+
myapi webhook list
|
|
25
|
+
myapi webhook delivery <delivery_id>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Authentication
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export MYAPI_KEY=mak_...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires `api_key` and `org_id` from **myapihq**.
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
Full command reference and workflow integration: see `SKILL.md`.
|
|
39
|
+
|
|
40
|
+
Run `myapi webhook --help` for inline reference.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-webhook-api
|
|
3
|
+
description: >
|
|
4
|
+
Inbound webhook endpoints. Receive HTTP POSTs from third parties (forms, Stripe, GitHub, etc.) and either inspect the deliveries directly or wire them to a workflow.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyWebhookAPI
|
|
8
|
+
|
|
9
|
+
Per-org HTTP endpoints that accept inbound POSTs and durably store every delivery. A webhook on its own is a passive receiver — pair it with **myworkflowapi** to react to incoming events (send email, post to Slack, etc.).
|
|
10
|
+
|
|
11
|
+
## How It Fits Together
|
|
12
|
+
|
|
13
|
+
- Requires `api_key` and `org_id` from **myapihq**.
|
|
14
|
+
- Each endpoint has a unique inbound URL (returned at create time).
|
|
15
|
+
- Deliveries are stored verbatim — full request body + headers — for inspection.
|
|
16
|
+
- `myworkflowapi` workflows bind to a webhook endpoint via its `id`.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Create a webhook endpoint
|
|
22
|
+
myapi webhook create "stripe-events" --description "Stripe payment events"
|
|
23
|
+
# → prints: Inbound URL: https://api.myapihq.com/webhook/in/<slug>
|
|
24
|
+
|
|
25
|
+
# 2. POST to the inbound URL from your service
|
|
26
|
+
curl -X POST https://api.myapihq.com/webhook/in/<slug> \
|
|
27
|
+
-H "Content-Type: application/json" \
|
|
28
|
+
-d '{"event":"payment.succeeded"}'
|
|
29
|
+
|
|
30
|
+
# 3. List endpoints / inspect a delivery
|
|
31
|
+
myapi webhook list
|
|
32
|
+
myapi webhook delivery <delivery_id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## All Commands
|
|
36
|
+
|
|
37
|
+
| Command | What it does |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `myapi webhook create` | Create an endpoint, returns id + inbound URL |
|
|
40
|
+
| `myapi webhook list` | List all endpoints in your org |
|
|
41
|
+
| `myapi webhook delete <id>` | Delete an endpoint and all its deliveries |
|
|
42
|
+
| `myapi webhook delivery <delivery_id>` | Get the full payload + headers for one delivery |
|
|
43
|
+
|
|
44
|
+
## Wiring to a Workflow
|
|
45
|
+
|
|
46
|
+
Webhooks become useful when a workflow fires on each inbound POST. See **myworkflowapi**:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Create the webhook
|
|
50
|
+
WID=$(myapi webhook create "leads" --json | jq -r .id)
|
|
51
|
+
|
|
52
|
+
# Bind a workflow that sends an email each time the webhook fires
|
|
53
|
+
myapi workflow create "Notify on lead" \
|
|
54
|
+
--endpoint-id $WID \
|
|
55
|
+
--steps '[{"type":"send_email","to":"sales@yourdomain.com","subject":"New lead"}]'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Payload contract
|
|
59
|
+
|
|
60
|
+
The inbound URL accepts **any JSON body** — there's no enforced schema, no
|
|
61
|
+
required fields. Whatever you POST is stored verbatim alongside the headers
|
|
62
|
+
and a timestamp. The shape is whatever the sender chooses to send.
|
|
63
|
+
|
|
64
|
+
That has two consequences:
|
|
65
|
+
|
|
66
|
+
1. **You decide the shape.** A form, a Stripe event, a custom payload — all
|
|
67
|
+
work. Be consistent within one webhook so downstream workflows can
|
|
68
|
+
reference fields reliably.
|
|
69
|
+
2. **Workflows reference fields with `{{ payload.field }}` templating.**
|
|
70
|
+
If your form POSTs `{"email": "alice@x.com", "message": "hi"}`, a
|
|
71
|
+
workflow step can do `--to "{{ payload.email }}"` to send a reply to
|
|
72
|
+
whoever submitted.
|
|
73
|
+
|
|
74
|
+
Inbound responses: 200 OK on success, 4xx if the endpoint is missing or
|
|
75
|
+
the body isn't valid JSON.
|
|
76
|
+
|
|
77
|
+
## End-to-end recipe — contact form on your website
|
|
78
|
+
|
|
79
|
+
Goal: a contact form on yoursite.com. When someone submits, send a
|
|
80
|
+
thank-you email to them and a Slack ping to your team.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 1. Create a webhook to receive form submissions
|
|
84
|
+
WID=$(myapi webhook create "contact-form" --json | jq -r .id)
|
|
85
|
+
WURL=$(myapi webhook list --json | jq -r ".[] | select(.id==\"$WID\") | .url")
|
|
86
|
+
|
|
87
|
+
# 2. Create the workflow that fires on every submission
|
|
88
|
+
myapi workflow create "Contact form handler" \
|
|
89
|
+
--endpoint-id $WID \
|
|
90
|
+
--steps "$(cat <<'JSON'
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
"type": "send_email",
|
|
94
|
+
"from": "hello@yoursite.com",
|
|
95
|
+
"to": "{{ payload.email }}",
|
|
96
|
+
"subject": "Thanks for reaching out, {{ payload.name }}!",
|
|
97
|
+
"body": "We got your message and will reply within 24 hours."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"type": "slack",
|
|
101
|
+
"webhook_url": "https://hooks.slack.com/services/T.../B.../xxx",
|
|
102
|
+
"text": "New contact form submission from {{ payload.name }} ({{ payload.email }}): {{ payload.message }}"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
JSON
|
|
106
|
+
)"
|
|
107
|
+
|
|
108
|
+
# 3. Drop this form in your HTML — it POSTs JSON to the webhook
|
|
109
|
+
echo "<form id='contact'>
|
|
110
|
+
<input name='name' required>
|
|
111
|
+
<input name='email' type='email' required>
|
|
112
|
+
<textarea name='message' required></textarea>
|
|
113
|
+
<button>Send</button>
|
|
114
|
+
</form>
|
|
115
|
+
<script>
|
|
116
|
+
document.getElementById('contact').addEventListener('submit', async e => {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
const data = Object.fromEntries(new FormData(e.target));
|
|
119
|
+
await fetch('$WURL', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: {'Content-Type': 'application/json'},
|
|
122
|
+
body: JSON.stringify(data),
|
|
123
|
+
});
|
|
124
|
+
alert('Thanks! Check your inbox.');
|
|
125
|
+
});
|
|
126
|
+
</script>"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The form sends `{"name": "...", "email": "...", "message": "..."}` →
|
|
130
|
+
webhook stores it → workflow fires → email goes out → Slack gets pinged.
|
|
131
|
+
|
|
132
|
+
## Notes
|
|
133
|
+
|
|
134
|
+
- The inbound URL is public — anyone with the URL can POST to it. Treat the slug as a secret.
|
|
135
|
+
- Deliveries are kept indefinitely. Delete the endpoint to purge them.
|
|
136
|
+
- Failed workflow runs do not affect the delivery record — the inbound POST is always saved.
|
|
137
|
+
|
|
138
|
+
Run `myapi webhook --help` for full flag reference.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
# my-workflow-api
|
|
3
|
+
|
|
4
|
+
Run a chain of actions (send email, post to Slack, call an HTTP endpoint) every time an inbound webhook fires. No backend required.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- Bind ordered step chains to webhook endpoints
|
|
9
|
+
- Steps can: send transactional email, hit Slack, call any HTTP URL
|
|
10
|
+
- Template values from the inbound webhook payload (`{{ payload.field }}`)
|
|
11
|
+
- Per-run status tracking (attempt, error, started/finished timestamps)
|
|
12
|
+
- Enable/disable without losing config
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Create a webhook + workflow that sends Slack on every POST
|
|
18
|
+
WID=$(myapi webhook create "leads" --json | jq -r .id)
|
|
19
|
+
myapi workflow create "Slack on lead" \
|
|
20
|
+
--endpoint-id $WID \
|
|
21
|
+
--steps '[{"type":"slack","webhook_url":"..."}]'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Authentication
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export MYAPI_KEY=mak_...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires `api_key` + `org_id` from **myapihq**, and a webhook endpoint id from **mywebhookapi**.
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
|
|
34
|
+
Full command reference, step types, and templating: see `SKILL.md`.
|
|
35
|
+
|
|
36
|
+
Run `myapi workflow --help` for inline reference.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-workflow-api
|
|
3
|
+
description: >
|
|
4
|
+
Run actions when a webhook fires. Trigger emails, Slack notifications, or HTTP calls in response to inbound webhook deliveries — without writing a backend.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyWorkflowAPI
|
|
8
|
+
|
|
9
|
+
Workflows bind a list of steps to a webhook endpoint. Every time the webhook receives a POST, the workflow runs its steps in order. Each run is recorded with its status, attempt count, and any error.
|
|
10
|
+
|
|
11
|
+
## How It Fits Together
|
|
12
|
+
|
|
13
|
+
- Requires `api_key` and `org_id` from **myapihq**.
|
|
14
|
+
- Requires a webhook endpoint id from **mywebhookapi** as the trigger.
|
|
15
|
+
- Steps can reference other services — e.g. send an email via **myemailapi**.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Create the webhook that will trigger the workflow
|
|
21
|
+
WID=$(myapi webhook create "leads" --json | jq -r .id)
|
|
22
|
+
|
|
23
|
+
# 2. Create a workflow bound to that webhook
|
|
24
|
+
myapi workflow create "Slack on new lead" \
|
|
25
|
+
--endpoint-id $WID \
|
|
26
|
+
--steps '[{"type":"slack","webhook_url":"https://hooks.slack.com/..."}]'
|
|
27
|
+
|
|
28
|
+
# 3. POST to the webhook → workflow fires
|
|
29
|
+
curl -X POST <webhook-inbound-url> -d '{"name":"Alice"}'
|
|
30
|
+
|
|
31
|
+
# 4. Inspect runs
|
|
32
|
+
myapi workflow runs <workflow_id>
|
|
33
|
+
myapi workflow get-run <run_id>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## All Commands
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `myapi workflow create` | Create a new workflow (`--name --endpoint-id --steps`) |
|
|
41
|
+
| `myapi workflow list` | List workflows in your org |
|
|
42
|
+
| `myapi workflow get <id>` | Inspect a workflow's steps + trigger config |
|
|
43
|
+
| `myapi workflow update <id>` | Change name, endpoint, or steps |
|
|
44
|
+
| `myapi workflow enable <id>` | Enable firing |
|
|
45
|
+
| `myapi workflow disable <id>` | Stop firing without deleting |
|
|
46
|
+
| `myapi workflow delete <id>` | Permanently remove workflow + run history |
|
|
47
|
+
| `myapi workflow runs <id>` | List recent runs (status, attempt, errors) |
|
|
48
|
+
| `myapi workflow get-run <run_id>` | Get full run details + step output |
|
|
49
|
+
|
|
50
|
+
## Steps
|
|
51
|
+
|
|
52
|
+
`--steps` is a JSON array of step objects. Each step has a `type` and type-specific fields.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
[
|
|
56
|
+
{
|
|
57
|
+
"type": "send_email",
|
|
58
|
+
"from": "hello@yourdomain.com",
|
|
59
|
+
"to": "{{ payload.email }}",
|
|
60
|
+
"subject": "Welcome, {{ payload.name }}",
|
|
61
|
+
"template_id": "<template_id>"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "http",
|
|
65
|
+
"method": "POST",
|
|
66
|
+
"url": "https://crm.example.com/contacts",
|
|
67
|
+
"body": "{{ payload | json }}"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The webhook payload is available as `{{ payload }}` and individual fields as `{{ payload.fieldname }}`.
|
|
73
|
+
|
|
74
|
+
## End-to-end recipe — react to a contact form submission
|
|
75
|
+
|
|
76
|
+
Goal: form on yoursite.com posts to a webhook → workflow sends a thank-you
|
|
77
|
+
email and pings Slack. Full HTML + commands:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. Create the webhook to receive submissions
|
|
81
|
+
WID=$(myapi webhook create "contact-form" --json | jq -r .id)
|
|
82
|
+
WURL=$(myapi webhook list --json | jq -r ".[] | select(.id==\"$WID\") | .url")
|
|
83
|
+
|
|
84
|
+
# 2. Workflow that fires on each submission. Two steps: thank-you email
|
|
85
|
+
# + Slack ping. Both reference fields from the form payload via {{ }}.
|
|
86
|
+
myapi workflow create "Contact form handler" \
|
|
87
|
+
--endpoint-id $WID \
|
|
88
|
+
--steps "$(cat <<'JSON'
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
"type": "send_email",
|
|
92
|
+
"from": "hello@yoursite.com",
|
|
93
|
+
"to": "{{ payload.email }}",
|
|
94
|
+
"subject": "Thanks for reaching out, {{ payload.name }}!",
|
|
95
|
+
"body": "We got your message and will reply within 24 hours."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "slack",
|
|
99
|
+
"webhook_url": "https://hooks.slack.com/services/T.../B.../xxx",
|
|
100
|
+
"text": "New submission from {{ payload.name }} ({{ payload.email }}): {{ payload.message }}"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
JSON
|
|
104
|
+
)"
|
|
105
|
+
|
|
106
|
+
# 3. The HTML form on yoursite.com — POSTs JSON to the webhook
|
|
107
|
+
echo "<form id='contact'>
|
|
108
|
+
<input name='name' required>
|
|
109
|
+
<input name='email' type='email' required>
|
|
110
|
+
<textarea name='message' required></textarea>
|
|
111
|
+
<button>Send</button>
|
|
112
|
+
</form>
|
|
113
|
+
<script>
|
|
114
|
+
document.getElementById('contact').addEventListener('submit', async e => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
const data = Object.fromEntries(new FormData(e.target));
|
|
117
|
+
await fetch('$WURL', {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {'Content-Type': 'application/json'},
|
|
120
|
+
body: JSON.stringify(data),
|
|
121
|
+
});
|
|
122
|
+
alert('Thanks! Check your inbox.');
|
|
123
|
+
});
|
|
124
|
+
</script>"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The form sends `{"name": "...", "email": "...", "message": "..."}`. The
|
|
128
|
+
webhook stores it. The workflow runs both steps in order: the user gets
|
|
129
|
+
an email at the address they typed, your team gets a Slack ping with the
|
|
130
|
+
message. Each invocation is recorded — `myapi workflow runs <id>` shows
|
|
131
|
+
the history.
|
|
132
|
+
|
|
133
|
+
## Lifecycle
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Create disabled, test, then enable
|
|
137
|
+
myapi workflow create "X" --endpoint-id $WID --steps '...' --no-enable
|
|
138
|
+
|
|
139
|
+
# Trigger via webhook POST, inspect
|
|
140
|
+
myapi workflow runs <id>
|
|
141
|
+
|
|
142
|
+
# When ready
|
|
143
|
+
myapi workflow enable <id>
|
|
144
|
+
|
|
145
|
+
# To temporarily stop
|
|
146
|
+
myapi workflow disable <id>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Notes
|
|
150
|
+
|
|
151
|
+
- Workflows fire on every webhook delivery. Failed runs don't block subsequent runs.
|
|
152
|
+
- Each run records its attempt number, started_at/finished_at, and any error.
|
|
153
|
+
- A workflow can be disabled to stop firing without losing the configuration.
|
|
154
|
+
- Deleting a workflow purges its run history; the webhook endpoint remains.
|
|
155
|
+
|
|
156
|
+
Run `myapi workflow --help` for full flag reference.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
export declare function parseArgs(argv: string[]): {
|
|
2
|
-
args: string[];
|
|
3
|
-
flags: Record<string, string | boolean>;
|
|
4
|
-
};
|
|
5
1
|
export declare function sleep(ms: number): Promise<unknown>;
|
|
6
2
|
/**
|
|
7
3
|
* Formats an ISO/Go timestamp string to "YYYY-MM-DD HH:mm" (UTC).
|
|
8
4
|
* Strips Go's " +0000 UTC" suffix before parsing.
|
|
9
5
|
*/
|
|
10
6
|
export declare function formatDate(str: string): string;
|
|
7
|
+
export interface PollOptions<T> {
|
|
8
|
+
/** Fetch current state. Called once per interval. */
|
|
9
|
+
check: () => Promise<T>;
|
|
10
|
+
/** Inspect a state and decide if we should resolve with it. */
|
|
11
|
+
isDone: (state: T) => boolean;
|
|
12
|
+
/** Inspect a state and decide if the job has failed (calls error()). */
|
|
13
|
+
isFailed?: (state: T) => boolean;
|
|
14
|
+
/** Label shown next to the spinner (e.g. "Importing"). */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Total wall-clock budget. Defaults to 90s. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
/** Delay between checks. Defaults to 3s. */
|
|
19
|
+
intervalMs?: number;
|
|
20
|
+
/** Message shown via error() on timeout. Defaults to a generic phrase. */
|
|
21
|
+
timeoutMessage?: string;
|
|
22
|
+
/** Message shown via error() when isFailed returns true. */
|
|
23
|
+
failedMessage?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generic spinner+poll helper. Used wherever the CLI kicks off a long-running
|
|
27
|
+
* server-side job and needs to wait for it (template generate, sync-brand).
|
|
28
|
+
*
|
|
29
|
+
* Returns the final `T` from `check()` once `isDone(state)` is true.
|
|
30
|
+
* Calls error() (which exits with code 1) on timeout or `isFailed`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pollJob<T>(opts: PollOptions<T>): Promise<T>;
|
package/dist/utils.js
CHANGED
|
@@ -1,36 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const args = [];
|
|
3
|
-
const flags = {};
|
|
4
|
-
for (let i = 0; i < argv.length; i++) {
|
|
5
|
-
const arg = argv[i];
|
|
6
|
-
if (arg === '-h') {
|
|
7
|
-
flags['help'] = true;
|
|
8
|
-
}
|
|
9
|
-
else if (arg === '-v') {
|
|
10
|
-
flags['version'] = true;
|
|
11
|
-
}
|
|
12
|
-
else if (arg.startsWith('--')) {
|
|
13
|
-
if (arg.includes('=')) {
|
|
14
|
-
const [key, value] = arg.slice(2).split('=', 2);
|
|
15
|
-
flags[key] = value;
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
const next = argv[i + 1];
|
|
19
|
-
if (next && !next.startsWith('--')) {
|
|
20
|
-
flags[arg.slice(2)] = next;
|
|
21
|
-
i++;
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
flags[arg.slice(2)] = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
args.push(arg);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return { args, flags };
|
|
33
|
-
}
|
|
1
|
+
import { spinnerFrame, clearLine, error } from './output.js';
|
|
34
2
|
export function sleep(ms) {
|
|
35
3
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
36
4
|
}
|
|
@@ -45,3 +13,34 @@ export function formatDate(str) {
|
|
|
45
13
|
return str;
|
|
46
14
|
return date.toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
|
|
47
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Generic spinner+poll helper. Used wherever the CLI kicks off a long-running
|
|
18
|
+
* server-side job and needs to wait for it (template generate, sync-brand).
|
|
19
|
+
*
|
|
20
|
+
* Returns the final `T` from `check()` once `isDone(state)` is true.
|
|
21
|
+
* Calls error() (which exits with code 1) on timeout or `isFailed`.
|
|
22
|
+
*/
|
|
23
|
+
export async function pollJob(opts) {
|
|
24
|
+
const timeoutMs = opts.timeoutMs ?? 90_000;
|
|
25
|
+
const intervalMs = opts.intervalMs ?? 3_000;
|
|
26
|
+
process.stdout.write(`${opts.label} `);
|
|
27
|
+
let i = 0;
|
|
28
|
+
let elapsed = 0;
|
|
29
|
+
while (elapsed < timeoutMs) {
|
|
30
|
+
const state = await opts.check();
|
|
31
|
+
if (opts.isDone(state)) {
|
|
32
|
+
clearLine();
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
if (opts.isFailed && opts.isFailed(state)) {
|
|
36
|
+
clearLine();
|
|
37
|
+
error(opts.failedMessage ?? `${opts.label} failed`);
|
|
38
|
+
}
|
|
39
|
+
process.stdout.write(`\r${opts.label} ${spinnerFrame(i++)}`);
|
|
40
|
+
await sleep(intervalMs);
|
|
41
|
+
elapsed += intervalMs;
|
|
42
|
+
}
|
|
43
|
+
clearLine();
|
|
44
|
+
error(opts.timeoutMessage ??
|
|
45
|
+
`${opts.label} timed out after ${Math.round(timeoutMs / 1000)}s. The job may still complete in the background — re-run to retry.`);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseArgs } from './utils.js';
|
|
3
|
+
describe('parseArgs', () => {
|
|
4
|
+
it('treats unknown flags as boolean', () => {
|
|
5
|
+
const { args, flags } = parseArgs(['funnel', 'get', '--json', 'abc-123']);
|
|
6
|
+
expect(flags.json).toBe(true);
|
|
7
|
+
expect(args).toEqual(['funnel', 'get', 'abc-123']);
|
|
8
|
+
});
|
|
9
|
+
it('treats --yes, --help, --verbose as boolean', () => {
|
|
10
|
+
const { flags } = parseArgs(['--yes', '--help', '--verbose']);
|
|
11
|
+
expect(flags.yes).toBe(true);
|
|
12
|
+
expect(flags.help).toBe(true);
|
|
13
|
+
expect(flags.verbose).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('consumes next token for value flags', () => {
|
|
16
|
+
const { args, flags } = parseArgs(['org', 'create', '--name', 'Acme Inc', '--yes']);
|
|
17
|
+
expect(flags.name).toBe('Acme Inc');
|
|
18
|
+
expect(flags.yes).toBe(true);
|
|
19
|
+
expect(args).toEqual(['org', 'create']);
|
|
20
|
+
});
|
|
21
|
+
it('supports --key=value syntax for any flag', () => {
|
|
22
|
+
const { flags } = parseArgs(['--org=abc-123', '--json']);
|
|
23
|
+
expect(flags.org).toBe('abc-123');
|
|
24
|
+
expect(flags.json).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('value flag with no next token becomes boolean true', () => {
|
|
27
|
+
const { flags } = parseArgs(['--name']);
|
|
28
|
+
expect(flags.name).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('value flag followed by another flag does not consume it', () => {
|
|
31
|
+
const { flags } = parseArgs(['--org', '--json']);
|
|
32
|
+
expect(flags.org).toBe(true);
|
|
33
|
+
expect(flags.json).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('-h maps to help', () => {
|
|
36
|
+
const { flags } = parseArgs(['-h']);
|
|
37
|
+
expect(flags.help).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('-v maps to version', () => {
|
|
40
|
+
const { flags } = parseArgs(['-v']);
|
|
41
|
+
expect(flags.version).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('collects positional args correctly', () => {
|
|
44
|
+
const { args, flags } = parseArgs(['domain', 'register', 'example.com', '--org', 'uuid-here']);
|
|
45
|
+
expect(args).toEqual(['domain', 'register', 'example.com']);
|
|
46
|
+
expect(flags.org).toBe('uuid-here');
|
|
47
|
+
});
|
|
48
|
+
});
|