@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.
Files changed (77) hide show
  1. package/dist/commands/auth.d.ts +8 -3
  2. package/dist/commands/auth.js +84 -60
  3. package/dist/commands/billing.d.ts +8 -4
  4. package/dist/commands/billing.js +46 -27
  5. package/dist/commands/config.d.ts +8 -5
  6. package/dist/commands/config.js +52 -27
  7. package/dist/commands/domain.d.ts +12 -9
  8. package/dist/commands/domain.js +124 -86
  9. package/dist/commands/email/campaign.d.ts +2 -0
  10. package/dist/commands/email/campaign.js +152 -0
  11. package/dist/commands/email/index.d.ts +4 -0
  12. package/dist/commands/email/index.js +98 -0
  13. package/dist/commands/email/mailbox.d.ts +2 -0
  14. package/dist/commands/email/mailbox.js +88 -0
  15. package/dist/commands/email/message.d.ts +2 -0
  16. package/dist/commands/email/message.js +115 -0
  17. package/dist/commands/email/template.d.ts +2 -0
  18. package/dist/commands/email/template.js +106 -0
  19. package/dist/commands/email/warmup.d.ts +2 -0
  20. package/dist/commands/email/warmup.js +43 -0
  21. package/dist/commands/email.d.ts +4 -12
  22. package/dist/commands/email.js +528 -146
  23. package/dist/commands/funnel.d.ts +10 -7
  24. package/dist/commands/funnel.js +79 -55
  25. package/dist/commands/image.js +25 -15
  26. package/dist/commands/keys.d.ts +8 -3
  27. package/dist/commands/keys.js +71 -35
  28. package/dist/commands/org.d.ts +9 -5
  29. package/dist/commands/org.js +100 -64
  30. package/dist/commands/pixel.js +23 -11
  31. package/dist/commands/setup.d.ts +3 -2
  32. package/dist/commands/setup.js +160 -165
  33. package/dist/commands/storage.js +25 -15
  34. package/dist/commands/update.d.ts +2 -1
  35. package/dist/commands/update.js +5 -0
  36. package/dist/commands/url.js +19 -7
  37. package/dist/commands/webhook.d.ts +8 -5
  38. package/dist/commands/webhook.js +70 -38
  39. package/dist/commands/workflow.d.ts +13 -7
  40. package/dist/commands/workflow.js +179 -58
  41. package/dist/config.js +10 -5
  42. package/dist/flags.d.ts +8 -0
  43. package/dist/flags.js +88 -0
  44. package/dist/flags.test.d.ts +1 -0
  45. package/dist/flags.test.js +73 -0
  46. package/dist/helpers.d.ts +6 -0
  47. package/dist/helpers.js +31 -0
  48. package/dist/index.js +98 -109
  49. package/dist/output.d.ts +12 -1
  50. package/dist/output.js +16 -6
  51. package/dist/prompt.d.ts +24 -0
  52. package/dist/prompt.js +41 -0
  53. package/dist/skills/my-email-api/README.md +45 -0
  54. package/dist/skills/my-email-api/SKILL.md +104 -0
  55. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
  56. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  57. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  58. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  59. package/dist/skills/my-webhook-api/README.md +40 -0
  60. package/dist/skills/my-webhook-api/SKILL.md +138 -0
  61. package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
  62. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  63. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  64. package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
  65. package/dist/skills/my-workflow-api/README.md +36 -0
  66. package/dist/skills/my-workflow-api/SKILL.md +156 -0
  67. package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
  68. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  69. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  70. package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
  71. package/dist/utils.d.ts +26 -4
  72. package/dist/utils.js +32 -33
  73. package/dist/utils.test.d.ts +1 -0
  74. package/dist/utils.test.js +48 -0
  75. package/package.json +9 -4
  76. package/dist/commands/account.d.ts +0 -4
  77. 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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "my-email-api",
3
+ "description": "Send transactional and bulk email from your own domain. Mailboxes, AI templates, drip campaigns, and warmup.",
4
+ "version": "1.0.0",
5
+ "published": true
6
+ }
File without changes
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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "my-webhook-api",
3
+ "description": "Receive inbound HTTP webhooks. Per-org endpoints with full delivery history.",
4
+ "version": "1.0.0",
5
+ "published": true
6
+ }
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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "my-workflow-api",
3
+ "description": "React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.",
4
+ "version": "1.0.0",
5
+ "published": true
6
+ }
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
- export function parseArgs(argv) {
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
+ });