Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ jobs:
unzip -t "$zip"
done

- name: Build release notes
run: ./scripts/release-notes.sh "${{ github.ref_name }}" > release-notes.md

- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
dist/skills/*.zip \
--title "${{ github.ref_name }}" \
--generate-notes
--notes-file release-notes.md
14 changes: 13 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Overview

Public skills repository for Gravity products. Pure content repo — no build tooling or package manager required. Skills are distributed via GitHub Releases (zips). The `@gravity/skills` CLI lives in the Gravity Monorepo.
Public skills repository for Gravity products. Pure content repo — no build tooling or package manager required. Skills are distributed via the [Vercel Skills CLI](https://github.com/vercel-labs/skills) (`npx skills add gravityforms/gravityskills`) and via GitHub Releases (zips).

## Critical Warnings

Expand All @@ -29,6 +29,18 @@ Public skills repository for Gravity products. Pure content repo — no build to
|---------|----------|---------|
| GitHub Releases (zips) | `.github/workflows/release.yml` | `v*` tag push |

**Tag only after merge to `main`.** The release workflow fires on `v*` tag push: it packs the skill zips, extracts the matching `change_log.txt` section via `scripts/release-notes.sh`, and creates the GitHub Release with that section as the notes. Never tag from a feature branch.

## Changelog

Record user-facing changes in `change_log.txt` (Gravity Forms core format). Add entries to the top version section in the same PR as the change — there is no fragment tooling.

- Header: `### <version> | <date>` (e.g. `### 1.0.2 | 2026-06-18`), newest version on top.
- One change per `- ` bullet, prefixed with the skill name it applies to (e.g. `gravity-forms-abilities: ...`); use `Repository:` for repo-wide changes.
- After the prefix, write in **present tense** ending in a period (e.g. `Add ...`, `Fix ...`, `Remove ...`, `Update ...`).
- Alpha-sort the full bullet lines within a version. This groups them by skill (prefix sorts first), then by verb.
- Skip internal-only changes (refactors, tests, CI, docs).

## Skills Directory Structure

Skills live in `skills/` and must follow the Agent Skills spec:
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Skills follow the [Agent Skills](https://agentskills.io/) format.

### Skills CLI

> Until the `@gravity/skills` CLI is published, use the [Vercel Skills CLI](https://github.com/vercel-labs/skills):
Install all skills with the [Vercel Skills CLI](https://github.com/vercel-labs/skills):

```bash
npx skills add gravityforms/gravityskills
Expand Down
11 changes: 11 additions & 0 deletions change_log.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### 1.0.2 | 2026-06-18
- gravity-forms-abilities: Add a warning against deprecated Ready Classes in favor of the layout grid.
- gravity-forms-abilities: Add documentation for the 100-entry bulk-delete cap and the count/delete loop.
- gravity-forms-abilities: Add pricing field guidance covering product, option, quantity, shipping, and total inputTypes and submission formats.
- gravity-forms-abilities: Update the access model to the per-tool allowlist (Enable MCP plus individual tool checkboxes), replacing the read/write toggles.

### 1.0.1 | 2026-05-13
- gravity-forms-abilities: Add the skill — workflow guidance for Gravity Forms abilities via the WordPress Abilities API (MCP).

### 1.0.0 | 2026-05-13
- Repository: Add the public Gravity Skills repository framework.
56 changes: 56 additions & 0 deletions scripts/release-notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Prints the change_log.txt section for a given version, for use as GitHub Release notes.
# Usage: ./scripts/release-notes.sh 1.0.2 (leading "v" is stripped)
#
# Self-check: ./scripts/release-notes.sh --self-check

set -euo pipefail

CHANGELOG="${CHANGELOG:-change_log.txt}"

extract() {
local version="${1#v}" file="$2"
# Print the bullet lines under "### <version>" up to the next "### " header.
awk -v ver="$version" '
/^### / {
if (found) exit
# Header is "### <version>" optionally followed by " | <date>".
hdr = $2
if (hdr == ver) { found = 1; next }
}
found && NF { print }
' "$file"
}

if [[ "${1:-}" == "--self-check" ]]; then
tmp="$(mktemp)"
cat >"$tmp" <<-'EOF'
### 1.0.2 | 2026-06-18
- Add b.
- Add a.

### 1.0.1 | 2026-05-13
- Add the skill.
EOF
got="$(extract v1.0.2 "$tmp")"
want=$'- Add b.\n- Add a.'
[[ "$got" == "$want" ]] || { echo "FAIL latest: got [$got]" >&2; exit 1; }
got="$(extract 1.0.1 "$tmp")"
[[ "$got" == "- Add the skill." ]] || { echo "FAIL middle: got [$got]" >&2; exit 1; }
[[ -z "$(extract 9.9.9 "$tmp")" ]] || { echo "FAIL missing version not empty" >&2; exit 1; }
rm -f "$tmp"
echo "self-check passed"
exit 0
fi

if [[ $# -lt 1 ]]; then
echo "Usage: $0 <version>" >&2
exit 1
fi

notes="$(extract "$1" "$CHANGELOG")"
if [[ -z "$notes" ]]; then
echo "Error: no $CHANGELOG section found for version ${1#v}" >&2
exit 1
fi
printf '%s\n' "$notes"
25 changes: 15 additions & 10 deletions skills/gravity-forms-abilities/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ All ability names use format `gravityforms/{category}-{action}`.

## Access Modes

The MCP settings page (GF Settings → MCP) controls two toggles:
The MCP settings page (GF Settings → MCP) gates access with a master switch plus a per-tool allowlist:

1. **Enable MCP** (default: off) — Gates all ability registration. When off, no `gravityforms/*` abilities appear.
2. **Write Access** (default: off) — When off, only read-only abilities are registered (12 of 25). Write abilities (create, update, delete, submit, send, add) are filtered out at registration time.
1. **Enable MCP** (default: off) — Master switch. When off, no `gravityforms/*` ability is exposed or callable, regardless of the per-tool checkboxes.
2. **Per-tool checkboxes** (default: every tool off) — When MCP is on, each ability is individually enabled or disabled via a checkbox, grouped into **Read-only Tools** and **Write & Destructive Tools** (each with a select/deselect-all control). A tool is exposed and callable only when its box is checked. **There is no blanket "read access" — read-only tools are off by default too** and must be enabled individually.

**Read-only abilities** (always available when MCP is enabled):
`forms-get`, `forms-list`, `forms-analyze-logic`, `entries-get`, `entries-search`, `entries-count`, `submissions-validate`, `feeds-list`, `notifications-list`, `notes-list`, `system-info`, `system-field-types`
So an ability is available only when **Enable MCP is on AND that specific tool is checked**. A freshly enabled integration exposes nothing until tools are opted in.

**Write abilities** (require Write Access toggle):
`forms-create`, `forms-update`, `forms-delete`, `forms-duplicate`, `entries-create`, `entries-update`, `entries-delete`, `feeds-create`, `feeds-update`, `feeds-delete`, `submissions-submit`, `notifications-send`, `notes-add`
**Read-only group** (each opt-in): `forms-get`, `forms-list`, `forms-analyze-logic`, `entries-get`, `entries-search`, `entries-count`, `submissions-validate`, `feeds-list`, `notifications-list`, `notes-list`, `system-info`, `system-field-types`

If a write ability is not available, the site admin has not enabled write access. Do not attempt workarounds — inform the user that write access must be enabled in GF Settings → MCP.
**Write & destructive group** (each opt-in): `forms-create`, `forms-update`, `forms-delete`, `forms-duplicate`, `entries-create`, `entries-update`, `entries-delete`, `feeds-create`, `feeds-update`, `feeds-delete`, `submissions-submit`, `notifications-send`, `notes-add`

If an ability is not available (absent from discovery, or a direct call is permission-denied), the admin has not enabled that specific tool (or MCP itself is off). Do not attempt workarounds — tell the user to enable the exact tool, and Enable MCP, in GF Settings → MCP.

### Endpoint Modes

Expand Down Expand Up @@ -218,7 +218,9 @@ See [references/entry-operations.md](references/entry-operations.md) for filter

### Bulk Deleting Entries

`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all matching entries server-side in one call.
`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all matching entries server-side.

**Bulk calls are capped at 100 entries per call** (to avoid server timeouts). The response includes `remaining` and `capped`. When `capped: true`, loop: call `entries-count` for the updated count, then `entries-delete` again (force mode needs a fresh `DELETE {count} ENTRIES FROM FORM {form_id}` confirmation each time). Repeat until `remaining` is 0. Already-trashed entries are excluded from trash-mode bulk deletes automatically, so each call processes new entries.

**Delete all entries from every form** (e.g., pre-launch cleanup):
1. Call `forms-list` — get all form IDs
Expand Down Expand Up @@ -320,14 +322,15 @@ For CL structure details, operators, and common patterns, see [references/condit
| Using `@example.com` emails in submissions | Rejected as spam by GF email field | Use realistic test domains (e.g., `@testmail.dev`) |
| Passing `form_id` as top-level param on `forms-update` | Input validation error | Put form ID inside the `form` object as `id` |
| Using integer `fieldId` in CL rules | Logic may not evaluate correctly | Always use string: `"fieldId": "1"` not `"fieldId": 1` |
| Using deprecated Ready Classes (`gf_left_half`, `gf_right_half`, `gf_left_third`, `gf_inline`, etc.) in `cssClass` | Deprecated since GF 2.5 — the API strips them and reports `stripped_ready_classes` in the response | NEVER use Ready Classes for layout. Use `layoutGroupId` + `layoutGridColumnSpan` — see Layout Grid in field-config reference |
Comment thread
faction23 marked this conversation as resolved.
| Parsing raw CL from `forms-get` manually | Error-prone, misses notifications/confirmations/buttons | Use `forms-analyze-logic` instead |
| Using `is_active: '0'` to pause a form | Form disappears entirely — page shows nothing | Use `scheduleForm` + `scheduleEnd` instead |
| Fileupload field without `allowedExtensions`/`maxFileSize` | Accepts any file type/size — security risk | Always set `allowedExtensions` and `maxFileSize` — see field-config reference |
| Creating a form without `notifications` | Submissions saved but no email sent — admin never notified | Always include a notification object — see "Creating a Form" workflow |
| Creating a name field without `nameFormat: "advanced"` | First/Last sub-inputs render stacked vertically instead of side-by-side | Always set `"nameFormat": "advanced"` and `"size": "large"` on name fields — see field-config reference |
| Passing multiselect values as comma-separated string | Data loss when values contain commas (e.g., "Atlanta, GA") | Always pass multiselect values as an **array**: `"input_1": ["Red", "Blue"]` — see field-config reference |
| User asks to "create a form and add it to a page" | Cannot create WordPress pages/posts — only GF abilities exist | Create the form, then tell the user the shortcode `[gravityform id="X" title="true"]` to embed manually. Page/post creation is not currently available via the Abilities API. |
| Write ability not found (e.g., `forms-create`) | Site admin has not enabled Write Access in MCP settings | Inform the user that write access must be enabled in GF Settings → MCP before write operations are available. Do not attempt workarounds. |
| Ability not found (e.g., `forms-create`) | Site admin has not checked that specific tool (or Enable MCP is off) in MCP settings | Tell the user to enable the exact tool — and Enable MCP — in GF Settings → MCP. There is no blanket "write access" toggle; each tool is opted in individually. Do not attempt workarounds. |
| `system-field-types` does not list rating/survey-style fields | The required add-on is not active on this site | Call `system-info` to check active add-ons; fall back to core fields (`radio`, `select`, `checkbox`) when add-on types are unavailable |
| `entries-update` with only status/metadata | Older bridge versions could wipe omitted field values | Safest pattern is still fetch → merge → update when changing existing entries |
| `entries-create` date field given in the wrong format | Searches may miss the entry later | Prefer the form's configured date format; ISO is only safe if the bridge explicitly normalizes it |
Expand All @@ -347,6 +350,8 @@ For CL structure details, operators, and common patterns, see [references/condit

**Layout grid**: Control field width and row grouping with `layoutGridColumnSpan` (1–12, default full width) and `layoutGroupId` (any string — fields sharing the same value render on the same row). See [references/field-config.md](references/field-config.md) §Layout Grid for patterns and natural language mapping.

**Pricing fields**: `product`, `option`, `quantity`, `shipping`, `total`. Product variants (single product, dropdown, calculation, hidden, user-defined price) are set via `inputType` on a `product` field — never as standalone types (`singleproduct`, `calculation`, etc. are not valid `type` values and are excluded from `system-field-types`). Choice-based products submit `value|price` (e.g. `"input_2": "Pro|30"` for a $30.00 choice). See [references/field-config.md](references/field-config.md) §Pricing Fields for the full pattern table and submission formats.

**Type-specific properties**: Several field types accept configuration beyond what `system-field-types` reports — `number` (rangeMin/rangeMax/numberFormat), `date` (dateFormat/dateType), `phone` (phoneFormat), `text`/`textarea` (maxLength), `fileupload` (allowedExtensions/maxFileSize/maxFiles), `consent` (checkboxLabel/description). See [references/field-config.md](references/field-config.md) for the full reference.

For detailed field type tables, compound field suffix mappings, and form design patterns, see [references/field-config.md](references/field-config.md).
Expand Down
13 changes: 11 additions & 2 deletions skills/gravity-forms-abilities/references/entry-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ For large result sets:

## Bulk Entry Deletion

`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all entries for a form in a single call.
`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all entries for a form server-side.

### Delete all entries for one form

Expand All @@ -135,7 +135,16 @@ For large result sets:
}
```

Returns: `{ "success": true, "deleted_count": 47 }`
Returns: `{ "success": true, "deleted_count": 47, "trashed": true, "remaining": 0, "capped": false, "cap": 100 }`

### Per-call cap (100 entries)

Bulk calls process at most 100 entries per call to avoid server timeouts. When more entries match:

- `capped: true` and `remaining > 0` are returned
- Loop: call `entries-count` for the updated count, then `entries-delete` again, until `remaining` is 0
- **Force mode**: each loop iteration needs a fresh confirmation phrase built from the updated count (`DELETE {count} ENTRIES FROM FORM {form_id}`)
- **Trash mode**: already-trashed entries are excluded automatically, so each call picks up where the last one left off; filtering on `status: "trash"` with trash mode is a no-op (`deleted_count: 0`)

### Delete with filter (e.g., only entries before a date)

Expand Down
26 changes: 26 additions & 0 deletions skills/gravity-forms-abilities/references/field-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,30 @@ Choice field types: `select`, `radio`, `checkbox`, `multiselect`, `image_choice`

Note: Always call `system-field-types` for the authoritative, up-to-date list — add-ons may register additional types.

## Pricing Fields (`product`, `option`, `quantity`, `shipping`, `total`)

Pricing variants are NOT standalone field types. `system-field-types` intentionally omits `singleproduct`, `hiddenproduct`, `calculation`, `price`, `donation`, and `singleshipping` — those are **`inputType` values on a `product` (or `shipping`) field**, matching the editor's "Field Type" dropdown. Never pass them as `type`.

| What you want | Field config |
|---|---|
| Fixed-price product | `{ "type": "product", "label": "Widget", "inputType": "singleproduct", "basePrice": "$25.00" }` |
| Product chosen from a list | `{ "type": "product", "inputType": "select", "choices": [{ "text": "Pro", "value": "Pro", "price": "$30.00" }] }` (also `radio`) |
| User-defined price | `{ "type": "product", "inputType": "price" }` |
| Hidden product | `{ "type": "product", "inputType": "hiddenproduct", "basePrice": "$9.00" }` |
| Calculated price | `{ "type": "product", "inputType": "calculation", "calculationFormula": "{Qty:3} * 2" }` |
| Quantity for a product | `{ "type": "quantity", "label": "Qty", "productField": 1 }` (`productField` = the product field's ID) |
| Flat shipping | `{ "type": "shipping", "inputType": "singleshipping", "basePrice": "$5.00" }` |
| Shipping options | `{ "type": "shipping", "inputType": "select", "choices": [{ "text": "Ground", "value": "Ground", "price": "$5.00" }] }` |
| Order total | `{ "type": "total", "label": "Total" }` |

The API applies the same defaults the editor would: bare `type: product` becomes `inputType: singleproduct`; single/hidden/calculation products get their `.1` (name) / `.2` (price) / `.3` (quantity) sub-inputs created automatically; choice-based product and shipping fields get `enablePrice` set (required — without it, priced submissions fail GF's anti-tampering state validation).

**Submitting pricing fields:**

- Single product: `"input_1.1": "Widget"`, `"input_1.2": "$25.00"`, and `"input_1.3": "2"` for quantity (or use a separate quantity field: `"input_3": "2"`).
- Choice-based product/shipping: submit `value|price` with the price as a plain number matching the choice — e.g. `"input_2": "Pro|30"` for a `$30.00` choice. The wrong price (or omitting `|price`) fails validation as a tampered submission.
- Total fields are computed server-side — never submit a value for them.

## Type-Specific Configuration

`system-field-types` returns generic capability flags but does **not** expose type-specific properties. The following properties are accepted by `forms-create` and `forms-update` — pass them directly on the field object.
Expand Down Expand Up @@ -296,6 +320,8 @@ The `is` operator does not work for individual multiselect values — it matches

Fields default to full-width (12 columns). To place fields side-by-side, give them the same `layoutGroupId` and set `layoutGridColumnSpan` to control width.

**NEVER use Ready Classes.** Gravity Forms' legacy CSS helper classes (`gf_left_half`, `gf_right_half`, `gf_left_third`, `gf_middle_third`, `gf_right_third`, `gf_first_quarter`…`gf_fourth_quarter`, `gf_inline`, `gf_list_*`) are deprecated since GF 2.5 and do nothing in modern themes. The abilities API strips them from `cssClass` automatically and returns a `stripped_ready_classes` list plus a `notice` when it does. The layout grid is the only supported way to control field layout.

### Properties

| Property | Type | Description |
Expand Down
Loading