Skip to content

Commit 58fce6f

Browse files
committed
feat(forms): add create, edit, and embed commands
Add full CRUD support for forms (create via POST /forms/new, edit via PATCH /forms/{id}/edit) and an embed command that generates JS script tag, iframe, or raw HTML embed codes matching the Mautic UI output. Bump version to 0.1.4.
1 parent 73d32b4 commit 58fce6f

7 files changed

Lines changed: 148 additions & 4 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
| **Segments** | List, create, edit, delete, add/remove contacts |
2525
| **Emails** | List, create, edit, send to contact, send to segment |
2626
| **Campaigns** | List, create, edit, delete, clone (7+), add/remove contacts |
27-
| **Forms** | List, get, submissions, delete |
27+
| **Forms** | List, create, edit, delete, submissions, embed code |
2828
| **Companies** | List, get, create, edit, delete, add/remove contacts |
2929
| **Notes** | List (per contact), get, create |
3030
| **Stages** | List, set contact stage |
@@ -111,7 +111,13 @@ mautic campaigns clone 1 # Mautic 7+ only
111111
```bash
112112
mautic forms list
113113
mautic forms get 1
114+
mautic forms create --json @form.json
115+
mautic forms edit 1 --json '{"name":"Updated Form"}'
114116
mautic forms submissions 1
117+
mautic forms embed 1 # show JS + iframe embed codes
118+
mautic forms embed 1 --type js # script tag only
119+
mautic forms embed 1 --type iframe # iframe only
120+
mautic forms embed 1 --type html # raw cached HTML
115121
mautic forms delete 1
116122
```
117123

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mautic-cli"
7-
version = "0.1.3"
7+
version = "0.1.4"
88
description = "Control Mautic from the command line or any AI coding agent."
99
readme = "README.md"
1010
license = "MIT"

skills/mautic/SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ mautic campaigns remove-contact <CAMPAIGN_ID> <CONTACT_ID>
118118
```bash
119119
mautic forms list [--search QUERY] [--limit N] [--offset N]
120120
mautic forms get <ID>
121+
mautic forms create --json '<DATA>'
122+
mautic forms edit <ID> --json '<DATA>'
121123
mautic forms submissions <ID> [--limit N]
124+
mautic forms embed <ID> [--type js|iframe|html]
122125
mautic forms delete <ID>
123126
```
124127

src/mautic_cli/commands/forms.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from __future__ import annotations
22

3+
import json as json_mod
4+
from urllib.parse import urlparse
5+
36
import click
47

58
from mautic_cli.context import pass_context, MauticContext
69
from mautic_cli.client import MauticApiError
10+
from mautic_cli.json_input import parse_json_input
711

812

913
@click.group()
@@ -58,6 +62,72 @@ def submissions(mctx: MauticContext, id, limit):
5862
raise SystemExit(1)
5963

6064

65+
@forms.command()
66+
@click.option("--json", "json_str", required=True, help="JSON data or @file.")
67+
@pass_context
68+
def create(mctx: MauticContext, json_str):
69+
"""Create a new form."""
70+
try:
71+
payload = parse_json_input(json_str)
72+
data = mctx.client.post("/forms/new", json=payload)
73+
mctx.output(data)
74+
except MauticApiError as e:
75+
mctx.error(e)
76+
raise SystemExit(1)
77+
78+
79+
@forms.command()
80+
@click.argument("id", type=int)
81+
@click.option("--json", "json_str", required=True, help="JSON data or @file.")
82+
@pass_context
83+
def edit(mctx: MauticContext, id, json_str):
84+
"""Edit an existing form."""
85+
try:
86+
payload = parse_json_input(json_str)
87+
data = mctx.client.patch(f"/forms/{id}/edit", json=payload)
88+
mctx.output(data)
89+
except MauticApiError as e:
90+
mctx.error(e)
91+
raise SystemExit(1)
92+
93+
94+
@forms.command()
95+
@click.argument("id", type=int)
96+
@click.option("--type", "embed_type", default=None, type=click.Choice(["js", "iframe", "html"]),
97+
help="Embed type: js (script tag), iframe, or html (raw cachedHtml). Omit to show all.")
98+
@pass_context
99+
def embed(mctx: MauticContext, id, embed_type):
100+
"""Get form embed code."""
101+
base = mctx.client.base_url.rstrip("/")
102+
parsed = urlparse(base)
103+
host = parsed.hostname
104+
if parsed.port and parsed.port not in (80, 443):
105+
host = f"{host}:{parsed.port}"
106+
107+
js_code = f'<script type="text/javascript" src="//{host}/form/generate.js?id={id}"></script>'
108+
iframe_code = (f'<iframe src="//{host}/form/{id}" width="300" height="300">'
109+
f"<p>Your browser does not support iframes.</p></iframe>")
110+
111+
if embed_type == "js":
112+
click.echo(js_code)
113+
elif embed_type == "iframe":
114+
click.echo(iframe_code)
115+
elif embed_type == "html":
116+
try:
117+
data = mctx.client.get(f"/forms/{id}")
118+
form = data.get("form", data)
119+
click.echo(form.get("cachedHtml", ""))
120+
except MauticApiError as e:
121+
mctx.error(e)
122+
raise SystemExit(1)
123+
else:
124+
click.echo("Via Javascript (recommended)")
125+
click.echo(js_code)
126+
click.echo()
127+
click.echo("Via iframe")
128+
click.echo(iframe_code)
129+
130+
61131
@forms.command()
62132
@click.argument("id", type=int)
63133
@pass_context

tests/test_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def test_version_flag():
77
runner = CliRunner()
88
result = runner.invoke(cli, ["--version"])
99
assert result.exit_code == 0
10-
assert "0.1.0" in result.output
10+
assert "mautic-cli, version" in result.output
1111

1212

1313
def test_help_shows_global_flags():

tests/test_forms.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,71 @@ def test_form_submissions(self):
5151
result = runner.invoke(cli, ["forms", "submissions", "1"])
5252
assert result.exit_code == 0
5353

54+
@respx.mock
55+
def test_create_form(self):
56+
respx.post("https://mautic.test/api/forms/new").mock(
57+
return_value=httpx.Response(200, json={
58+
"form": {"id": 5, "name": "New Form"},
59+
})
60+
)
61+
runner = CliRunner(env=MOCK_ENV)
62+
result = runner.invoke(cli, [
63+
"forms", "create",
64+
"--json", '{"name": "New Form", "formType": "standalone", "postAction": "message", "postActionProperty": "Thanks!"}'
65+
])
66+
assert result.exit_code == 0
67+
data = json.loads(result.output)
68+
assert data["form"]["name"] == "New Form"
69+
70+
@respx.mock
71+
def test_edit_form(self):
72+
respx.patch("https://mautic.test/api/forms/5/edit").mock(
73+
return_value=httpx.Response(200, json={
74+
"form": {"id": 5, "name": "Updated Form"},
75+
})
76+
)
77+
runner = CliRunner(env=MOCK_ENV)
78+
result = runner.invoke(cli, [
79+
"forms", "edit", "5",
80+
"--json", '{"name": "Updated Form"}'
81+
])
82+
assert result.exit_code == 0
83+
data = json.loads(result.output)
84+
assert data["form"]["name"] == "Updated Form"
85+
86+
def test_embed_default(self):
87+
runner = CliRunner(env=MOCK_ENV)
88+
result = runner.invoke(cli, ["forms", "embed", "4"])
89+
assert result.exit_code == 0
90+
assert "Via Javascript (recommended)" in result.output
91+
assert "Via iframe" in result.output
92+
assert "generate.js?id=4" in result.output
93+
assert 'src="//mautic.test/form/4"' in result.output
94+
95+
def test_embed_js(self):
96+
runner = CliRunner(env=MOCK_ENV)
97+
result = runner.invoke(cli, ["forms", "embed", "4", "--type", "js"])
98+
assert result.exit_code == 0
99+
assert result.output.strip() == '<script type="text/javascript" src="//mautic.test/form/generate.js?id=4"></script>'
100+
101+
def test_embed_iframe(self):
102+
runner = CliRunner(env=MOCK_ENV)
103+
result = runner.invoke(cli, ["forms", "embed", "4", "--type", "iframe"])
104+
assert result.exit_code == 0
105+
assert 'src="//mautic.test/form/4"' in result.output
106+
107+
@respx.mock
108+
def test_embed_html(self):
109+
respx.get("https://mautic.test/api/forms/4").mock(
110+
return_value=httpx.Response(200, json={
111+
"form": {"id": 4, "name": "Test", "cachedHtml": "<div>form html</div>"},
112+
})
113+
)
114+
runner = CliRunner(env=MOCK_ENV)
115+
result = runner.invoke(cli, ["forms", "embed", "4", "--type", "html"])
116+
assert result.exit_code == 0
117+
assert result.output.strip() == "<div>form html</div>"
118+
54119
@respx.mock
55120
def test_delete_form(self):
56121
respx.delete("https://mautic.test/api/forms/1/delete").mock(

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)