@gaffa-dev/skills 0.1.0
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/README.md +88 -0
- package/package.json +23 -0
- package/skills/gaffa-authoring/SKILL.md +177 -0
- package/skills/gaffa-authoring/references/actions.md +235 -0
- package/skills/gaffa-authoring/templates/async-poll.md +58 -0
- package/skills/gaffa-authoring/templates/schema-extraction.md +72 -0
- package/skills/gaffa-authoring/templates/sync.md +31 -0
- package/skills/gaffa-debug/SKILL.md +161 -0
- package/skills/gaffa-debug/references/failure-classification.md +29 -0
- package/skills/gaffa-debug/templates/rerun-with-capture.md +31 -0
- package/skills/gaffa-find/SKILL.md +159 -0
- package/skills/gaffa-find/templates/loop.md +105 -0
- package/skills/gaffa-support/SKILL.md +145 -0
- package/skills/gaffa-support/agents/openai.yaml +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# gaffa-for-ai
|
|
2
|
+
|
|
3
|
+
Four agent skills for building on the [gaffa.dev](https://gaffa.dev) browser-automation REST API, plus the Gaffa docs MCP server, installable as one plugin.
|
|
4
|
+
|
|
5
|
+
| Skill | Entry | What it does |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `gaffa-authoring` | auto-invokes on a gaffa coding prompt | Writes, edits, ports, or reviews code that calls the gaffa API. Loads verified facts and live docs before generating code. |
|
|
8
|
+
| `gaffa-find` | `/gaffa-find` | Iteratively discovers and extracts one piece of information from a target site, given a URL and a plain-language goal. |
|
|
9
|
+
| `gaffa-debug` | `/gaffa-debug` | Diagnoses a failing request or `brq_*` id from its recording and proposes a minimal patch. |
|
|
10
|
+
| `gaffa-support` | `/gaffa-support` (slash only) | Helps when stuck: tries to resolve the problem first, then packages a redacted local report to email to support. Slash only on Claude Code, Cursor, Codex and Copilot. Antigravity has no way to opt a skill out of automatic selection, so there the agent can still reach for it. |
|
|
11
|
+
|
|
12
|
+
## Install as a plugin
|
|
13
|
+
|
|
14
|
+
This repo is a plugin. Installing it brings in all four skills and registers the Gaffa docs MCP server in one step, so there is nothing to copy and nothing to configure.
|
|
15
|
+
|
|
16
|
+
For Claude Code and Codex, point the tool's plugin marketplace at this repository, then install the `gaffa` plugin from it. Cursor and Antigravity install differently and are covered below.
|
|
17
|
+
|
|
18
|
+
On Claude Code that is two commands:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
claude plugin marketplace add https://github.com/GaffaAI/gaffa-for-ai
|
|
22
|
+
claude plugin install gaffa@gaffa --scope project
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The full URL is deliberate. Claude Code clones the `owner/repo` shorthand over SSH by default, which fails without a GitHub SSH key.
|
|
26
|
+
|
|
27
|
+
Scopes are `user` (the default), `project`, which writes `.claude/settings.json` in the project you are working in and is shared with collaborators, and `local`, which writes `.claude/settings.local.json` in that same project and is not shared.
|
|
28
|
+
|
|
29
|
+
Two things worth knowing:
|
|
30
|
+
|
|
31
|
+
- Cursor has no command-line install. The plugin manifest is here and the plugin loads once Cursor has it, but adding it is a click in the app rather than something you can script.
|
|
32
|
+
- Updating differs by tool. Claude Code refreshes marketplaces in the background by default and updates an installed plugin with `/plugin update`, where `/plugin marketplace update` only re-pulls the catalog. The others need asking: `codex plugin marketplace upgrade`, `copilot plugin update`, `cursor-agent plugin marketplace update`.
|
|
33
|
+
|
|
34
|
+
Antigravity has no marketplace concept, so it installs by putting the plugin where it looks. Clone this repo into its plugins directory and restart:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
git clone --depth 1 https://github.com/GaffaAI/gaffa-for-ai ~/.gemini/config/plugins/gaffa
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That covers Antigravity 2.0 and the IDE. The CLI stages plugins under `~/.gemini/antigravity-cli/plugins/` instead, so use that path if you run `agy`.
|
|
41
|
+
|
|
42
|
+
For one project rather than the whole machine, clone into `.agents/plugins/gaffa` instead. To commit it for the team, drop the clone's own history first, otherwise git records a pointer to another repository and a teammate ends up with an empty directory:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
git clone --depth 1 https://github.com/GaffaAI/gaffa-for-ai .agents/plugins/gaffa
|
|
46
|
+
rm -rf .agents/plugins/gaffa/.git
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Install by copying
|
|
50
|
+
|
|
51
|
+
Each skill is a self-contained folder that follows the open Agent Skills standard (a `SKILL.md` with `name` and `description` frontmatter), so it works in any tool that reads that format. Copy the folders you want out of `skills/` into the directory your tool reads:
|
|
52
|
+
|
|
53
|
+
| Tool | Project directory | Personal directory |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Claude Code | `.claude/skills/` | `~/.claude/skills/` |
|
|
56
|
+
| Cursor (2.4+) | `.agents/skills/` or `.cursor/skills/` | `~/.agents/skills/` or `~/.cursor/skills/` |
|
|
57
|
+
| Codex CLI | `.agents/skills/` | `~/.agents/skills/` |
|
|
58
|
+
| GitHub Copilot | `.agents/skills/` | `~/.agents/skills/` |
|
|
59
|
+
| Antigravity | `.agents/skills/` | see below |
|
|
60
|
+
|
|
61
|
+
Antigravity's personal directory depends on which surface you run. Its own docs give `~/.gemini/config/skills/` for Antigravity 2.0, `~/.gemini/antigravity/skills/` for the IDE and `~/.gemini/antigravity-cli/skills/` for the CLI. The project directory is the same for all three, so prefer that one.
|
|
62
|
+
|
|
63
|
+
For example, to install `gaffa-authoring` into a project on Claude Code:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
cp -r skills/gaffa-authoring .claude/skills/gaffa-authoring
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Swap the target for your tool. Copy only the skills you want, they do not depend on each other. Restart or reload the tool after adding a skill so it picks them up.
|
|
70
|
+
|
|
71
|
+
Copying this way brings the skills but not the docs MCP server, which the plugin install registers for you. To add it by hand, point your tool at `https://gaffa.dev/docs/~gitbook/mcp`. It takes no authentication. The two config files in this repo are the shapes to copy: `.mcp.json` for Claude Code and Cursor, and `mcp_config.json` for Antigravity, which uses `serverUrl` where the others use a type and a url. Without it the skills fall back to fetching the docs over plain HTTP, which works and is just slower.
|
|
72
|
+
|
|
73
|
+
The agent selects a skill automatically from its `description` when the task matches. The slash commands in the table above are a Claude Code affordance. Codex reaches the same skills as `$gaffa-find` or through its picker. In other tools, describe the task and the agent invokes the right skill.
|
|
74
|
+
|
|
75
|
+
## Setup
|
|
76
|
+
|
|
77
|
+
Set up two things once before running the skills.
|
|
78
|
+
|
|
79
|
+
1. API key. The skills read the key from the `GAFFA_API_KEY` environment variable only. It is never prompted for, written to disk, or logged. Set it persistently rather than pasting it each session, for example `export GAFFA_API_KEY=...` in your shell profile. The key stays out of version control.
|
|
80
|
+
2. Network egress. The skills call `api.gaffa.dev` and read docs from `gaffa.dev`. Behind a sandbox or proxy that blocks outbound traffic, allow both hosts first, or requests fail as connectivity errors.
|
|
81
|
+
|
|
82
|
+
The first real request confirms the setup. A missing or invalid key returns a clear authentication error, and a blocked host surfaces as a connectivity error that points back to the egress step.
|
|
83
|
+
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- Gaffa has no official SDK. The skills emit REST calls in whatever language and library your project already uses.
|
|
87
|
+
- The gaffa docs MCP server makes documentation lookups faster. The plugin install registers it for you, and the copy-install section above covers adding it by hand.
|
|
88
|
+
- The skills spend gaffa credits. `/gaffa-find` enforces a tunable cost cap set in its `SKILL.md`. `/gaffa-debug` spends credits only on an optional re-run after explicit confirmation. `/gaffa-support` spends no credits and makes no gaffa API call.
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gaffa-dev/skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Gaffa skills for writing code against the Gaffa browser-automation API.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Impetus Labs Ltd",
|
|
8
|
+
"email": "support@gaffa.dev"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://gaffa.dev",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/GaffaAI/gaffa-for-ai.git"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"skills",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"provenance": true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gaffa-authoring
|
|
3
|
+
description: Use when the user mentions gaffa in a programming context (a gaffa.dev URL, a gaffa endpoint, or an existing gaffa API call) and wants concrete code. Writes, edits, ports, or reviews code that calls the gaffa.dev browser-automation REST API, loading the verified gaffa API facts and live docs first. For triaging a failing request or a brq_* id, /gaffa-debug leads instead. For a URL with only a vague goal and no script, /gaffa-find leads.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# gaffa authoring
|
|
7
|
+
|
|
8
|
+
Helps a developer write, edit, port, or hand-review code that calls the gaffa.dev browser-automation REST API.
|
|
9
|
+
Gaffa is a plain HTTP REST API with no official SDK.
|
|
10
|
+
You emit code in whatever language and style the developer's project already uses.
|
|
11
|
+
|
|
12
|
+
## Critical gaffa facts (grounding)
|
|
13
|
+
|
|
14
|
+
For anything beyond them, consult the live docs (see Doc-fetching strategy).
|
|
15
|
+
|
|
16
|
+
1. Auth header is `X-API-Key: <key>`.
|
|
17
|
+
Read from `GAFFA_API_KEY` env var.
|
|
18
|
+
Never hard-code.
|
|
19
|
+
2. `POST /v1/browser/requests` is async by default.
|
|
20
|
+
Returns an id.
|
|
21
|
+
Poll `GET /v1/browser/requests/{id}`.
|
|
22
|
+
Opt into sync with `"async": false`.
|
|
23
|
+
3. Max runtime is plan-tiered (1 / 2 / 5 min) for both sync and async.
|
|
24
|
+
Always set `settings.time_limit` explicitly.
|
|
25
|
+
4. `parse_json` is token-priced, so its cost scales with the content parsed rather than being a flat per-call charge.
|
|
26
|
+
Stored `/v1/schemas` extractions run the same `parse_json` action and are priced the same way.
|
|
27
|
+
Check the live docs for the current model and token rates.
|
|
28
|
+
Other actions are deterministically priced.
|
|
29
|
+
5. Request recordings (`settings.record_request: true`) are strongly recommended for `/gaffa-debug`.
|
|
30
|
+
Without one, the skill can only suggest re-running the failing request with recording enabled.
|
|
31
|
+
Plan-tiered retention applies (7 days / 30 days / 3 months).
|
|
32
|
+
|
|
33
|
+
The API base URL is `https://api.gaffa.dev`.
|
|
34
|
+
Every `/v1/...` endpoint is called on that host.
|
|
35
|
+
The documentation and the docs MCP live on `https://gaffa.dev`.
|
|
36
|
+
API responses are wrapped in a top-level `data` object, so read fields as `data.id`, `data.state`, `data.credit_usage`, and `data.actions`.
|
|
37
|
+
A finished request has `data.state` equal to `completed`.
|
|
38
|
+
Each action result is a URL in `data.actions[].output`.
|
|
39
|
+
The gaffa edge rejects some default HTTP-client User-Agents (for example Python `urllib`) with a 403, so emitted code should set an explicit `User-Agent` header.
|
|
40
|
+
curl works with its default User-Agent.
|
|
41
|
+
In the request body, `actions`, `time_limit`, and `record_request` go under `settings`, while `url`, `async`, `max_cache_age`, and `proxy_location` are root-level.
|
|
42
|
+
`time_limit` is in milliseconds.
|
|
43
|
+
The `parse_json` action uses a structured `data_schema` of the form `{name, description, fields: [{type, name, description}]}`, never a flat object and never a `schema` or `prompt` field.
|
|
44
|
+
An optional `instruction` parameter sits beside `data_schema` (not inside it) for extra parsing guidance.
|
|
45
|
+
`/v1/schemas` is an endpoint for reusable stored schemas, not an action type.
|
|
46
|
+
LLM-backed extraction runs through the `parse_json` action, with an inline `data_schema` or a stored `data_schema_id`, and there is no separate schema action type.
|
|
47
|
+
`parse_json` is not the only way to get data off a page, see "Choosing an extraction action" below.
|
|
48
|
+
On a large content-rich page, `parse_json` over the full DOM can fail with `action_failed` (verified on Wikipedia), so narrow the input with a `selector` for the region that holds the data, or set `input_token_cap`.
|
|
49
|
+
|
|
50
|
+
### Verified endpoint and field reference
|
|
51
|
+
|
|
52
|
+
- Endpoints: `POST/GET /v1/browser/requests`, `GET /v1/browser/requests/{id}`, `POST/GET /v1/schemas`, `PUT /v1/schemas/{id}`, `DELETE /v1/schemas/{id}`, `POST/GET /v1/site/map`, `GET /v1/site/map/{id}`.
|
|
53
|
+
The path is singular `map`, easy to typo as `maps`.
|
|
54
|
+
Schema update and delete both take the id in the path.
|
|
55
|
+
- Settings fields under `settings`: `actions`, `time_limit`, `record_request`, `max_media_bandwidth`, `block_ads`.
|
|
56
|
+
`max_cache_age` and `proxy_location` are root-level body fields, not under `settings`.
|
|
57
|
+
- `max_cache_age` is in seconds.
|
|
58
|
+
Set it to 0 to disable the cross-user cache.
|
|
59
|
+
- If `time_limit` is not set, it defaults to your plan's maximum runtime, and it must stay below that maximum.
|
|
60
|
+
Set it explicitly so the value is visible and intentional.
|
|
61
|
+
- Available actions: `click`, `scroll`, `type`, `wait`, `capture_cookies`, `capture_dom`, `capture_screenshot`, `capture_snapshot`, `download_file`, `generate_markdown`, `generate_simplified_dom`, `parse_json`, `print`, `block_dom_removals`, `capture_element`, `parse_table`, and `loop`, which repeats nested actions inside one request.
|
|
62
|
+
The per-action parameter catalog lives in `references/actions.md`.
|
|
63
|
+
Read it on demand when you need a specific action's parameters.
|
|
64
|
+
- Proxy locations are residential IPs: `us`, `ie`, `sg`, `fr`.
|
|
65
|
+
Set `proxy_location` to route through a residential IP in that country.
|
|
66
|
+
With none set, the request uses a generic datacenter IP.
|
|
67
|
+
|
|
68
|
+
### Choosing an extraction action
|
|
69
|
+
|
|
70
|
+
Pick the action before you look up its parameters.
|
|
71
|
+
`parse_json` is LLM-backed, so it is token-priced and its output can vary between runs.
|
|
72
|
+
Weigh that against a deterministic path first, and tell the developer the trade-off you took so they can overrule it.
|
|
73
|
+
|
|
74
|
+
- Prefer a deterministic path when the value sits in a stable, well-structured place.
|
|
75
|
+
Cheaper, repeatable, identical every run.
|
|
76
|
+
Good for a price in a known element, a list of cards with a consistent shape, a field in a JSON blob.
|
|
77
|
+
The options, in rough order of how often they fit:
|
|
78
|
+
- `parse_table` with a `selector` for a table, which returns the rows already structured.
|
|
79
|
+
- `generate_markdown` with a `selector` for the region, then parse the markdown in the developer's own language.
|
|
80
|
+
Good for repeating cards or list items, and your recon capture is often this already.
|
|
81
|
+
- `capture_element` with a `selector` for one specific element.
|
|
82
|
+
- Reach for `parse_json` when the value is buried in free text or moves around from page to page (a salary somewhere inside a job description), or when the task is interpretive rather than a lookup (summarising, classifying).
|
|
83
|
+
That is where the LLM earns its cost.
|
|
84
|
+
- Do not use `parse_json` when the developer needs identical output across runs.
|
|
85
|
+
|
|
86
|
+
## Preferences
|
|
87
|
+
|
|
88
|
+
General preferences for using the Gaffa API.
|
|
89
|
+
They capture guidance beyond the API docs, and we add to them over time.
|
|
90
|
+
Follow them unless the specific task calls for something else.
|
|
91
|
+
|
|
92
|
+
- Prefer an inline `data_schema` over a stored `data_schema_id` for `parse_json`, so the end user can see the shape of what is being extracted.
|
|
93
|
+
Reach for a stored `data_schema_id` only when a shape is registered server-side for reuse across separate scripts or sessions.
|
|
94
|
+
|
|
95
|
+
## Credential hygiene
|
|
96
|
+
|
|
97
|
+
These rules apply to every line of code and every message this skill produces.
|
|
98
|
+
|
|
99
|
+
1. Read `GAFFA_API_KEY` from env only.
|
|
100
|
+
Never hard-code in emitted code.
|
|
101
|
+
Reference it as `${GAFFA_API_KEY}`.
|
|
102
|
+
2. Never echo, log, narrate, or persist the value of `GAFFA_API_KEY`.
|
|
103
|
+
Never put it in a URL or query string.
|
|
104
|
+
3. Before showing a gaffa recording, error trace, or request body to the user, to an LLM judge, or to disk, strip the values of any fields whose names match (case-insensitive, including vendor-prefixed variants like `gaffa_api_key`): `Authorization`, `X-API-Key`, `api_key` (and `apiKey`, `api-key`), `cookie`, `set-cookie`.
|
|
105
|
+
Replace the value with `<REDACTED>`.
|
|
106
|
+
4. If the runtime value of `GAFFA_API_KEY` appears as a substring anywhere in a payload you are about to show or persist, replace it with `<REDACTED>`.
|
|
107
|
+
Only enable this substring scrub when the env value is at least 16 characters long and contains both a digit and a letter.
|
|
108
|
+
Otherwise skip and warn the developer on first invocation that the entropy floor was not met (field-name and prose rules still apply).
|
|
109
|
+
5. Persisted writes (the `/gaffa-find` reasoning log `./.gaffa-find-<timestamp>.log`, and any other on-disk artifact) go through redaction first, then to a tempfile, then atomic-rename to the final path.
|
|
110
|
+
A crash mid-write must not leave a plaintext-secrets file on disk.
|
|
111
|
+
6. If unsure whether a string is a secret, redact it.
|
|
112
|
+
|
|
113
|
+
## Doc-fetching strategy
|
|
114
|
+
|
|
115
|
+
Resolve documentation queries in two tiers, in order.
|
|
116
|
+
|
|
117
|
+
1. Preferred: gaffa docs MCP server at `https://gaffa.dev/docs/~gitbook/mcp`.
|
|
118
|
+
When the developer has it configured, call its tools directly.
|
|
119
|
+
`searchDocumentation` (param `query`, a string) for "how do I do X" questions.
|
|
120
|
+
`getPage` (param `url`, a full URL) to fetch one page when you already have its URL.
|
|
121
|
+
2. Fallback: live HTTP fetch.
|
|
122
|
+
`?ask=` against the docs for narrow lookups, `https://gaffa.dev/docs/llms.txt` for breadth, `https://gaffa.dev/docs/sitemap.md` for the full page index.
|
|
123
|
+
Per-call timeout of 5 seconds.
|
|
124
|
+
Used when the MCP is not configured or returns an error.
|
|
125
|
+
|
|
126
|
+
MCP availability probe, once per session, cached, total budget 10 seconds:
|
|
127
|
+
|
|
128
|
+
1. JSON-RPC `initialize` over POST.
|
|
129
|
+
Confirms the endpoint speaks MCP.
|
|
130
|
+
2. Send the mandatory `notifications/initialized` notification.
|
|
131
|
+
3. JSON-RPC `tools/list` over POST.
|
|
132
|
+
Confirms `searchDocumentation` and `getPage` (or at least one) are present.
|
|
133
|
+
|
|
134
|
+
Per-call MCP timeout is 5 seconds.
|
|
135
|
+
If two consecutive calls in one session time out, demote to live HTTP for the rest of the session and tell the developer once.
|
|
136
|
+
|
|
137
|
+
If both tiers fail before any code is emitted, refuse with a clear message rather than guessing from training data.
|
|
138
|
+
Both tiers read from `gaffa.dev`, so when they fail together the usual cause is that `gaffa.dev` is blocked by the environment's egress policy, separate from `api.gaffa.dev`.
|
|
139
|
+
The message names that first: "Live gaffa docs are unavailable.
|
|
140
|
+
If `gaffa.dev` is blocked by your environment's egress or proxy policy, allow it and retry, see the setup section in the skills README.
|
|
141
|
+
Otherwise retry, or consult https://gaffa.dev/docs manually." If no gaffa MCP was configured, the message also suggests registering `https://gaffa.dev/docs/~gitbook/mcp` for faster lookups.
|
|
142
|
+
|
|
143
|
+
## First action
|
|
144
|
+
|
|
145
|
+
Before emitting code, live-fetch the doc section relevant to the developer's request (MCP preferred, HTTP fallback), grounded by the critical facts above.
|
|
146
|
+
Do not emit code before the relevant docs are confirmed or the grounding alone is sufficient and stated as such.
|
|
147
|
+
|
|
148
|
+
## Behavior
|
|
149
|
+
|
|
150
|
+
- Do not guess selectors.
|
|
151
|
+
When the code needs a selector (for `parse_json` with a `selector`, `capture_element`, `click`, `type`, or `wait`), do not infer it from the URL or from the existing code.
|
|
152
|
+
First fetch the real page with gaffa using a `generate_simplified_dom` or `capture_dom` capture, read the actual elements, then write the selector from what is really there.
|
|
153
|
+
If that fetch fails with a connectivity error, the page is unreachable, usually because egress to `api.gaffa.dev` is blocked, so point the developer at the egress setup instead of guessing.
|
|
154
|
+
If you genuinely cannot fetch the page, mark the selector as unverified and tell the developer to confirm it rather than presenting a guess as correct.
|
|
155
|
+
- Gaffa requests work best when targeting a single URL with actions performed on that page.
|
|
156
|
+
Pagination and other repeat-the-same-steps flows stay inside that one request with the `loop` action, capture first, then the click that moves on, see the catalog.
|
|
157
|
+
What remains unsupported is session state across separate requests, every request starts a fresh session, so surface that up front when the developer's request needs it.
|
|
158
|
+
- Always set `settings.time_limit` explicitly in generated code, based on what the job is expected to take.
|
|
159
|
+
You do not know the developer's plan max, so emit a code comment reminding the developer to verify the value fits their plan (Starter 1 min, Startup 2 min, Growth 5 min).
|
|
160
|
+
- For any request the developer may later want to debug or audit, set `settings.record_request: true` in the emitted code so `/gaffa-debug` has a recording to inspect within the retention window.
|
|
161
|
+
- Default to the async pattern (POST then poll the returned id).
|
|
162
|
+
Use `"async": false` only when the developer asks for a blocking call and the expected runtime is well under the plan max.
|
|
163
|
+
- Read the template that matches the task and adapt it to the developer's language and library.
|
|
164
|
+
They are starting points, not literal output.
|
|
165
|
+
- `templates/async-poll.md` for the POST-then-poll pattern.
|
|
166
|
+
- `templates/sync.md` for the blocking `"async": false` pattern.
|
|
167
|
+
- `templates/schema-extraction.md` for `/v1/schemas` structured extraction.
|
|
168
|
+
|
|
169
|
+
### Schema design and SDK migration
|
|
170
|
+
|
|
171
|
+
- Schema design with `/v1/schemas`.
|
|
172
|
+
Same authoring trigger, constrained to one endpoint.
|
|
173
|
+
See `templates/schema-extraction.md`.
|
|
174
|
+
Storing a schema uses the endpoint with a normal API key.
|
|
175
|
+
- Migration from Playwright, Puppeteer, or Selenium to gaffa.
|
|
176
|
+
Read the foreign snippet and map each step to a gaffa action, a pagination while-loop maps to the `loop` action.
|
|
177
|
+
Flag any step that relies on session state across separate requests as a known gaffa limitation.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# gaffa action parameter catalog
|
|
2
|
+
|
|
3
|
+
Per-action parameters, fetched from the live gaffa docs.
|
|
4
|
+
Read this on demand when you need a specific action's parameters.
|
|
5
|
+
When in doubt, re-confirm against the live docs (`getPage` on the action's page under `/docs/features/browser-requests/actions/`, or the HTTP fallback), because the docs can change.
|
|
6
|
+
|
|
7
|
+
## Universal parameters (every action)
|
|
8
|
+
|
|
9
|
+
- `type` (string, required): the action type identifier, for example `capture_dom`.
|
|
10
|
+
- `continue_on_fail` (boolean, optional): continue the run if this action fails.
|
|
11
|
+
Default `false`.
|
|
12
|
+
- `custom_id` (string, optional): a custom action identifier, echoed back on the matching action in the response.
|
|
13
|
+
Default `null`.
|
|
14
|
+
|
|
15
|
+
Actions run in the order they are submitted.
|
|
16
|
+
|
|
17
|
+
## Selectors
|
|
18
|
+
|
|
19
|
+
Every `selector` parameter below runs through Playwright's selector engine, not plain CSS.
|
|
20
|
+
All standard CSS works, and so do Playwright's extensions, text selectors such as `:has-text('foo')` and `:text('foo')`, `:visible`, and XPath.
|
|
21
|
+
Pseudo-elements such as `::before` cannot be targeted, there is no real element to return.
|
|
22
|
+
Elements inside same-origin iframes are reached with a plain selector, no frame targeting needed.
|
|
23
|
+
Cross-origin iframes are currently out of reach, a selector cannot match inside them.
|
|
24
|
+
Full reference: `/docs/features/browser-requests/selectors`.
|
|
25
|
+
|
|
26
|
+
## Actions without outputs
|
|
27
|
+
|
|
28
|
+
### click
|
|
29
|
+
- `selector` (string, required): selector for the element to click.
|
|
30
|
+
- `timeout` (integer, optional): max wait for the element.
|
|
31
|
+
Default 5000 ms.
|
|
32
|
+
|
|
33
|
+
Click waits up to its timeout for the element, so no `wait` before it.
|
|
34
|
+
Prefer stable selectors (`id`, `data-testid`, `aria-label`) over generated class names or positional ones.
|
|
35
|
+
Set `continue_on_fail: true` on clicks for cookie banners and pop-ups that may not appear.
|
|
36
|
+
After a click that loads new content, `wait` for that content before capturing.
|
|
37
|
+
|
|
38
|
+
### scroll
|
|
39
|
+
- `percentage` (integer, required): the position to scroll to, not a distance, so 50 is halfway down and 100 the bottom.
|
|
40
|
+
Default 100.
|
|
41
|
+
Scrolling only goes down, so emit 0 to 100.
|
|
42
|
+
- `wait_time` (integer, optional): after the scroll, keep watching the page and keep scrolling as loading content grows it.
|
|
43
|
+
Default 0.
|
|
44
|
+
- `max_scroll_time` (integer, optional): max scroll duration.
|
|
45
|
+
Default 20000 ms.
|
|
46
|
+
- `scroll_speed` (string, optional): one of `slow`, `medium`, `instant`.
|
|
47
|
+
Default `medium`.
|
|
48
|
+
- `interval` (integer, optional): pause between scroll events in ms. Default 0.
|
|
49
|
+
- `timeout` (integer, optional): wait for a scrollable element.
|
|
50
|
+
Default 0.
|
|
51
|
+
- `selector` (string, optional): element to scroll.
|
|
52
|
+
Defaults to the page body.
|
|
53
|
+
|
|
54
|
+
For infinite scroll, set `wait_time` so the scroll follows the growing page, and cap it with `max_scroll_time`.
|
|
55
|
+
Hitting `max_scroll_time` stops the scroll without failing, later actions still run.
|
|
56
|
+
Scroll a modal or side panel by passing its `selector`, scrolling the body will not move it.
|
|
57
|
+
If rows vanish while scrolling past them, put `block_dom_removals` before the scroll.
|
|
58
|
+
There is no scrolling back up, so order the actions to need one downward pass only.
|
|
59
|
+
|
|
60
|
+
### type
|
|
61
|
+
- `selector` (string, required): input field selector.
|
|
62
|
+
- `text` (string, required): text to enter.
|
|
63
|
+
- The docs currently list no `timeout` for this action.
|
|
64
|
+
If the form renders late, `wait` for the field first.
|
|
65
|
+
|
|
66
|
+
Typing does not submit, follow with a `click` on the submit button and a `wait` for what comes next.
|
|
67
|
+
Target the input element itself, not its wrapper.
|
|
68
|
+
Inside attribute selectors use single quotes (`input[name='email']`) so the selector needs no escaping in the JSON string.
|
|
69
|
+
One `type` action per field, and checkboxes, radios and dropdowns take a `click`, not a `type`.
|
|
70
|
+
|
|
71
|
+
### wait
|
|
72
|
+
- `time` (integer, optional): milliseconds to wait.
|
|
73
|
+
- `selector` (string, optional): selector to wait for.
|
|
74
|
+
- `timeout` (integer, optional): max wait for the selector.
|
|
75
|
+
Default 5000 ms.
|
|
76
|
+
|
|
77
|
+
Prefer a `selector` wait over a fixed `time`, it moves on as soon as the element appears.
|
|
78
|
+
Never set both, `time` silently wins and the selector is ignored.
|
|
79
|
+
No `wait` is needed before `click`, `capture_element`, or `parse_table`, they wait for their own selector.
|
|
80
|
+
Set `continue_on_fail: true` when the awaited element may never appear.
|
|
81
|
+
|
|
82
|
+
### block_dom_removals
|
|
83
|
+
- No parameters beyond the universal ones are documented.
|
|
84
|
+
|
|
85
|
+
Run it first, it only protects what happens after it and cannot restore what the page already removed.
|
|
86
|
+
It stays in force for the rest of the request.
|
|
87
|
+
The usual shape is `block_dom_removals`, then `scroll`, then the capture.
|
|
88
|
+
|
|
89
|
+
## Actions with outputs
|
|
90
|
+
|
|
91
|
+
### capture_cookies
|
|
92
|
+
- No parameters beyond the universal ones are documented.
|
|
93
|
+
|
|
94
|
+
Place it after the actions that set the cookies, so a login flow ends `type`, `click`, `wait`, then `capture_cookies`.
|
|
95
|
+
It returns cookie names and values only, no domain, expiry or flags.
|
|
96
|
+
Cookies cannot be replayed into another request, every browser request starts a fresh session.
|
|
97
|
+
Treat the output as credentials.
|
|
98
|
+
|
|
99
|
+
### capture_dom
|
|
100
|
+
- No parameters beyond the universal ones are documented.
|
|
101
|
+
|
|
102
|
+
Do not send the output to an LLM, `generate_simplified_dom` and `generate_markdown` carry nearly the same information in far fewer tokens.
|
|
103
|
+
There is no `selector` here, capturing part of the page means `capture_element`.
|
|
104
|
+
If content is missing from the capture, the page was not done loading, `wait` for a selector of the expected content first.
|
|
105
|
+
|
|
106
|
+
### capture_screenshot
|
|
107
|
+
- `size` (string, optional): one of `view`, `fullscreen`.
|
|
108
|
+
Default `view`.
|
|
109
|
+
|
|
110
|
+
`view` captures the visible viewport, `fullscreen` the whole page.
|
|
111
|
+
On pages that lazy-load images, scroll to 100 with a `wait_time` first or they come out blank.
|
|
112
|
+
There is no single-element screenshot, use `capture_element` for the HTML or scroll the element into view.
|
|
113
|
+
|
|
114
|
+
### capture_snapshot
|
|
115
|
+
- No parameters beyond the universal ones are documented.
|
|
116
|
+
|
|
117
|
+
The saved file has JavaScript switched off, so open tabs and expand sections with `click` and `wait` before capturing.
|
|
118
|
+
Reach for it when the page will be read or searched later, `capture_screenshot` only shows it, `print` makes a paper-style PDF.
|
|
119
|
+
|
|
120
|
+
### capture_element
|
|
121
|
+
- `selector` (string, required): selector for the target element.
|
|
122
|
+
- `timeout` (integer, optional): max wait for the element.
|
|
123
|
+
Default 5000 ms.
|
|
124
|
+
|
|
125
|
+
It waits for its selector up to the timeout, so no `wait` before it, and a generous timeout does not slow a successful run.
|
|
126
|
+
Only the first match is captured, target a shared container to get more than one element.
|
|
127
|
+
Prefer it over `capture_dom` when you know where the content lives.
|
|
128
|
+
|
|
129
|
+
### download_file
|
|
130
|
+
- `timeout` (integer, optional): max download wait.
|
|
131
|
+
Default 5000 ms.
|
|
132
|
+
|
|
133
|
+
Only the documented file types download: .pdf, .jpg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .img.
|
|
134
|
+
Each action returns the most recent download and consumes it, so emit one `download_file` per expected file, each with a `custom_id`.
|
|
135
|
+
The default 5000 ms suits small images, documents want more, the docs suggest 20000 ms as a starting point.
|
|
136
|
+
A file behind a link is a `click` first, then the download.
|
|
137
|
+
|
|
138
|
+
### generate_markdown
|
|
139
|
+
- `selector` (string, optional): selector for a specific element.
|
|
140
|
+
- `output_type` (string, optional): one of `file`, `inline`.
|
|
141
|
+
Default `file`.
|
|
142
|
+
|
|
143
|
+
The default choice for LLM-bound page content, and `output_type: "inline"` returns it in the response, which suits agents and short pages.
|
|
144
|
+
Pass a `selector` such as `main` or `article` to skip navigation and footers.
|
|
145
|
+
Tables survive as Markdown tables, but code that processes a table wants `parse_table` instead.
|
|
146
|
+
|
|
147
|
+
### generate_simplified_dom
|
|
148
|
+
- No parameters beyond the universal ones are documented.
|
|
149
|
+
|
|
150
|
+
The selector-discovery step: run it, read the stable ids and classes, then write the other actions' selectors from what is there.
|
|
151
|
+
Query strings are stripped from links, use `capture_dom` when URL parameters matter.
|
|
152
|
+
|
|
153
|
+
### print
|
|
154
|
+
- `size` (string, optional): paper size, `A4` is the only accepted value at the moment.
|
|
155
|
+
Default `A4`.
|
|
156
|
+
- `margin` (integer, optional): margin in pixels.
|
|
157
|
+
Default 20.
|
|
158
|
+
- `orientation` (string, optional): one of `portrait`, `landscape`.
|
|
159
|
+
Default `portrait`.
|
|
160
|
+
|
|
161
|
+
The PDF follows the site's print styles, so it can differ from the screen, `capture_screenshot` with `size: "fullscreen"` keeps the screen look.
|
|
162
|
+
Wide content such as tables wants `landscape`, edge-to-edge designs want `margin: 0`.
|
|
163
|
+
A page that already is a PDF wants `download_file`, not `print`.
|
|
164
|
+
|
|
165
|
+
### parse_json
|
|
166
|
+
Token-priced, so surface cost as token-based, not deterministic.
|
|
167
|
+
The documented rate for `gpt-4o-mini` is 1 credit per 20,000 input tokens and 1 credit per 10,000 output tokens, re-check the live docs before quoting it.
|
|
168
|
+
- `data_schema_id` (string, required if `data_schema` is absent): a saved schema identifier.
|
|
169
|
+
- `data_schema` (json, required if `data_schema_id` is absent): an inline schema definition.
|
|
170
|
+
It is a structured object with `name`, `description`, and a `fields` array where each field has `type`, `name`, and `description`.
|
|
171
|
+
The documented field types are `string`, `integer`, `decimal`, `double`, `boolean`, `datetime`, `object`, and `array`, where `object` and `array` take nested `fields`.
|
|
172
|
+
A flat object such as `{"title":"string"}` is rejected and the action fails.
|
|
173
|
+
- `instruction` (string, optional): additional parsing instructions.
|
|
174
|
+
This is a sibling parameter of `parse_json`, not a field inside `data_schema`.
|
|
175
|
+
- `model` (string, optional): the parsing model.
|
|
176
|
+
Defaults to `gpt-4o-mini`, which is also the only accepted value at the moment.
|
|
177
|
+
- `input_token_cap` (integer, optional): max source tokens.
|
|
178
|
+
Default 1000000.
|
|
179
|
+
- `selector` (string, optional): selector for a content subset.
|
|
180
|
+
- `output_type` (string, optional): one of `file`, `inline`.
|
|
181
|
+
Default `file`.
|
|
182
|
+
- `max_pages` (integer, optional): PDF page limit.
|
|
183
|
+
No default documented.
|
|
184
|
+
|
|
185
|
+
Write field descriptions as instructions, the expected format and what to do when the value is missing, not as labels.
|
|
186
|
+
A list of items is an `array` field with nested `fields` describing the item shape.
|
|
187
|
+
It reads online PDFs too, with `max_pages` capping what is sent to the model.
|
|
188
|
+
|
|
189
|
+
### parse_table
|
|
190
|
+
- `selector` (string, required): selector identifying the table.
|
|
191
|
+
- `timeout` (integer, optional): max wait for the table.
|
|
192
|
+
Default 5000 ms.
|
|
193
|
+
|
|
194
|
+
Headers become the property names, lowercased, and characters other than letters and numbers become underscores, so `Ticket Price (£)` comes back as `ticket_price_`, check the keys before mapping them.
|
|
195
|
+
Every value comes back as a string, convert numbers and dates on your side.
|
|
196
|
+
Only real `<table>` markup parses, a div grid takes `parse_json` or `capture_element`.
|
|
197
|
+
If the table lazy-loads rows, scroll and wait first so the rows exist when it runs.
|
|
198
|
+
There is no merged-cell handling, take `capture_dom` plus your own parser for those.
|
|
199
|
+
|
|
200
|
+
## Flow actions
|
|
201
|
+
|
|
202
|
+
### loop
|
|
203
|
+
Repeats its nested actions in order, so pagination or repeated interaction fits in one request.
|
|
204
|
+
- `actions` (action[], required): the actions each iteration runs, any type except another `loop`.
|
|
205
|
+
- `iterations` (integer, optional): fixed number of iterations, 1 to 100.
|
|
206
|
+
- `max_iterations` (integer, optional): upper bound on iterations when the count is open.
|
|
207
|
+
Default 10, range 1 to 1000.
|
|
208
|
+
With both set, the lower value wins.
|
|
209
|
+
- `timeout` (integer, optional): max duration of the whole loop, all iterations together, not each one.
|
|
210
|
+
Default 20000 ms.
|
|
211
|
+
- `stop_on_fail` (boolean, optional): end the loop when a nested action fails.
|
|
212
|
+
Default `true`.
|
|
213
|
+
A nested action with `continue_on_fail: true` does not end it.
|
|
214
|
+
|
|
215
|
+
The basic pagination shape is capture first, then the click to the next page, so the last page is still captured and the click failing on it is what ends the loop.
|
|
216
|
+
Size `timeout` for the whole loop, the 20 second default rarely covers many pages.
|
|
217
|
+
A loop that hits its timeout fails with `action_timed_out`, and the finished iterations keep their outputs.
|
|
218
|
+
Give the nested actions `custom_id`s, every iteration reports its outputs and the ids tell them apart.
|
|
219
|
+
Dismiss cookie banners before the loop with a `continue_on_fail: true` click, and `wait` for the pagination control before the loop starts.
|
|
220
|
+
Set `continue_on_fail: true` on the loop itself when actions follow it, otherwise an early exit cancels them with `action_cancelled`.
|
|
221
|
+
|
|
222
|
+
## Notes on parse_json
|
|
223
|
+
|
|
224
|
+
There is no free-form `prompt` parameter.
|
|
225
|
+
Extraction is driven by `data_schema` or `data_schema_id`, with `instruction` as an optional refinement.
|
|
226
|
+
Do not emit a `prompt` field for `parse_json`.
|
|
227
|
+
|
|
228
|
+
Verified behavior: running `parse_json` over the full DOM of a large, content-rich page (for example a Wikipedia article) returns `data.error: action_failed` with no `output`, even with a valid `data_schema`.
|
|
229
|
+
Narrow the input to fix it: pass a `selector` for the region that holds the data (cheaper, around 2 credits in testing), or set `input_token_cap` (worked but cost more, around 4 credits).
|
|
230
|
+
Small pages extract fine without either.
|
|
231
|
+
|
|
232
|
+
## When to use parse_json
|
|
233
|
+
|
|
234
|
+
The rule for choosing between `parse_json` and a deterministic path lives in "Choosing an extraction action" in `SKILL.md`, because the choice has to be made before you get here.
|
|
235
|
+
If you are reading this page to look up `parse_json` parameters and have not weighed a deterministic path yet, go back and do that first.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Template: async request plus poll
|
|
2
|
+
|
|
3
|
+
The default gaffa pattern.
|
|
4
|
+
`POST /v1/browser/requests` returns an id immediately.
|
|
5
|
+
Poll `GET /v1/browser/requests/{id}` until `state` is terminal.
|
|
6
|
+
|
|
7
|
+
Language-agnostic shape shown with curl.
|
|
8
|
+
Adapt to the developer's language and HTTP library.
|
|
9
|
+
Keep `time_limit` explicit and read the key from the environment.
|
|
10
|
+
|
|
11
|
+
## Submit
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl -sS -X POST https://api.gaffa.dev/v1/browser/requests \
|
|
15
|
+
-H "X-API-Key: ${GAFFA_API_KEY}" \
|
|
16
|
+
-H "Content-Type: application/json" \
|
|
17
|
+
-H "User-Agent: gaffa-skill/1.0" \
|
|
18
|
+
-d '{
|
|
19
|
+
"url": "https://example.com",
|
|
20
|
+
"max_cache_age": 0,
|
|
21
|
+
"settings": {
|
|
22
|
+
"time_limit": 60000,
|
|
23
|
+
"record_request": true,
|
|
24
|
+
"actions": [
|
|
25
|
+
{ "type": "capture_screenshot" }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The response is wrapped in a top-level `data` object, so the id is at `data.id`.
|
|
32
|
+
The 60000 ms above (60 seconds) is an example value, not the default.
|
|
33
|
+
If you omit `time_limit` it defaults to your plan's maximum runtime and must stay under that maximum.
|
|
34
|
+
Set it explicitly and raise it toward your plan max if the job needs longer (Starter 1 min, Startup 2 min, Growth 5 min).
|
|
35
|
+
|
|
36
|
+
## Poll
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -sS https://api.gaffa.dev/v1/browser/requests/REQUEST_ID \
|
|
40
|
+
-H "X-API-Key: ${GAFFA_API_KEY}"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Poll on an interval until the request is terminal.
|
|
44
|
+
Treat `data.state` of `completed` as success and a non-empty `data.error` as failure.
|
|
45
|
+
Keep polling on any other state rather than hard-coding a list of in-progress state names, since the exact in-progress vocabulary is not documented and a name you did not anticipate should not be misread as a failure.
|
|
46
|
+
Always bound the loop with a deadline so it cannot spin forever, for example `time_limit` plus a margin for queueing and network.
|
|
47
|
+
On reaching the deadline, stop and report a timeout rather than continuing to poll.
|
|
48
|
+
A finished run carries `data.actions` (each action result is a URL in `data.actions[].output`) plus `data.credit_usage`, `data.from_cache`, `data.started_at`, `data.completed_at`, `data.running_time`, and `data.page_load_time`.
|
|
49
|
+
A failed run carries `data.error` and `data.error_reason`.
|
|
50
|
+
If you set `record_request: true`, the response also carries `data.video`.
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- `record_request: true` lets `/gaffa-debug` inspect what happened later, within the plan-tiered retention window (7 days / 30 days / 3 months).
|
|
55
|
+
- `max_cache_age` is a root-level field, not under `settings`, and is in seconds.
|
|
56
|
+
Set it to 0 to force a fresh fetch and bypass the cross-user cache.
|
|
57
|
+
- Never place the key in the URL or query string.
|
|
58
|
+
It belongs only in the `X-API-Key` header.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Template: structured extraction (/v1/schemas and inline parse_json)
|
|
2
|
+
|
|
3
|
+
`/v1/schemas` stores a reusable structured-extraction shape that a browser request references by `data_schema_id`.
|
|
4
|
+
The endpoint is reached with a normal API key.
|
|
5
|
+
The extraction cost comes from the token-priced `parse_json` action that uses the schema, so warn the developer that the per-call cost is token-based rather than deterministic.
|
|
6
|
+
|
|
7
|
+
Two-step flow.
|
|
8
|
+
Create or update a schema, then reference it from a browser request.
|
|
9
|
+
Confirm the exact request and response shape against the live docs before emitting final code, because the schema body format can change.
|
|
10
|
+
|
|
11
|
+
## Create a schema
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl -sS -X POST https://api.gaffa.dev/v1/schemas \
|
|
15
|
+
-H "X-API-Key: ${GAFFA_API_KEY}" \
|
|
16
|
+
-H "Content-Type: application/json" \
|
|
17
|
+
-H "User-Agent: gaffa-skill/1.0" \
|
|
18
|
+
-d '{
|
|
19
|
+
"name": "product",
|
|
20
|
+
"description": "Extract product details",
|
|
21
|
+
"fields": [
|
|
22
|
+
{ "type": "string", "name": "title", "description": "the product title" },
|
|
23
|
+
{ "type": "string", "name": "price", "description": "the listed price" }
|
|
24
|
+
]
|
|
25
|
+
}'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A schema is a structured object: a `name`, a `description`, and a `fields` array where each field has `type`, `name`, and `description`.
|
|
29
|
+
The response carries the schema id.
|
|
30
|
+
Use `PUT /v1/schemas/{id}` to update, `GET /v1/schemas` to list, and `DELETE /v1/schemas/{id}` to remove.
|
|
31
|
+
Update and delete both take the id in the path.
|
|
32
|
+
Confirm the exact create body against the live docs before relying on it, because the schema body format can change.
|
|
33
|
+
|
|
34
|
+
## Alternative: parse_json inline
|
|
35
|
+
|
|
36
|
+
For one-off extraction without a stored schema, the `parse_json` action runs inside a browser request.
|
|
37
|
+
It is token-priced, so flag the cost as token-based rather than deterministic.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl -sS -X POST https://api.gaffa.dev/v1/browser/requests \
|
|
41
|
+
-H "X-API-Key: ${GAFFA_API_KEY}" \
|
|
42
|
+
-H "Content-Type: application/json" \
|
|
43
|
+
-H "User-Agent: gaffa-skill/1.0" \
|
|
44
|
+
-d '{
|
|
45
|
+
"url": "https://example.com",
|
|
46
|
+
"max_cache_age": 0,
|
|
47
|
+
"settings": {
|
|
48
|
+
"time_limit": 60000,
|
|
49
|
+
"record_request": true,
|
|
50
|
+
"actions": [
|
|
51
|
+
{
|
|
52
|
+
"type": "parse_json",
|
|
53
|
+
"data_schema": {
|
|
54
|
+
"name": "product",
|
|
55
|
+
"description": "Extract the product title and price",
|
|
56
|
+
"fields": [
|
|
57
|
+
{ "type": "string", "name": "title", "description": "the product title" },
|
|
58
|
+
{ "type": "string", "name": "price", "description": "the listed price" }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`parse_json` is driven by `data_schema` (inline) or `data_schema_id` (a saved schema).
|
|
68
|
+
The `data_schema` is a structured object with `name`, `description`, and a `fields` array of `{type, name, description}`.
|
|
69
|
+
A flat object like `{"title":"string"}` is rejected and the action fails.
|
|
70
|
+
There is no free-form `prompt` parameter.
|
|
71
|
+
An optional `instruction` parameter sits alongside `data_schema` for extra parsing guidance.
|
|
72
|
+
See `references/actions.md` for the full parameter list, and "Choosing an extraction action" in `SKILL.md` for whether `parse_json` is the right action here at all.
|