formctl
v0.1.1
Published
Turn browser-recorded web forms into safe, repeatable CLI commands.
Maintainers
Readme
formctl
formctl turns any browser form into a safe, repeatable CLI command.
Run a saved workflow. Preview first. Approve only when ready.
formctl is for developers, operators, and AI agents that need reliable automation for web forms with no useful API. One person records a form workflow into reviewable YAML, then everyone else can run it as a CLI command with dry-run artifacts and explicit approval before real submission.
Workflow request guide · Example before/after posts · Growth log · Trust and security notes · Comparison with Playwright, browser agents, and RPA · Why browser agents need form-specific CLIs
formctl submit expense-report --values values.json --dry-run --json
formctl submit expense-report --values values.json --approve --jsonWatch the real local demo video
Trust Artifacts
See Trust and security notes for dry-run, approval, audit log, selector breakage, and secret-handling details. See Comparison with Playwright, browser agents, and RPA for when formctl is the right layer.
Install
npm install -g formctl
npx formctl --version
npx formctl --help
npx formctl doctordoctor checks Node, the current workspace, and the Playwright Chromium browser used by record and submit. If the browser is missing, run:
npx playwright install chromiumTwo-Minute Local Demo
Run the demo locally:
npm install
npm run demoIn a second terminal:
npm run formctl -- workflows --json
npm run formctl -- validate expense-report --json
npm run formctl -- submit expense-report --values demo/expense-values.json --dry-run --json --headless
npm run formctl -- submit expense-report --values demo/expense-values.json --approve --json --headlessThat is the main loop: discover, validate, dry-run, approve.
The demo workflows are already checked in under .formctl/workflows/. Run npm run formctl -- inspect <workflow-name> --json to see required fields for expense-report, admin-invite, support-refund, vendor-onboarding, procurement-approval, crm-update, and compliance-attestation.
Use --values <path> when field flags would be hard to quote. Unknown JSON keys or unknown submit field flags are rejected as field_values_invalid before opening the browser.
Use --storage-state <path> to replay a protected form with a local Playwright storageState JSON file after the user has already logged in.
Storage state files can contain cookies and must stay local; do not commit or paste them into agent chat.
Use --resume-after-interaction only in a local interactive submit run after completing login, MFA, or CAPTCHA in the browser.
Interactive submit shows the dry-run.png screenshot path before asking you to type approve.
Run artifacts are written under .formctl/runs/<run-id>/:
summary.jsonfield-diff.jsonaudit.jsonldry-run.pngfor previewspost-submit.pngfor approved submissionsfailure.jsonandfailure.pngfor selector mismatches and interaction-required safe stops
Field diffs list the resolved values that will be set before submission, with file inputs redacted as [file].
Audit logs record selector checks, redacted field values, approval source, screenshots, field diff paths, and final result.
Workflow files include safety metadata for dry-run first, required approval, selector drift failure, and file-input redaction.
Workflow names may contain only letters, numbers, dots, underscores, and dashes.
Run formctl validate <workflow-name> --json before reviewing or sharing workflow YAML.
Invalid workflow checks include message and fix fields so agents can report a concrete repair.
Unreadable workflow YAML returns a readable-yaml check with message and fix fields.
Validation rejects unredacted recording.events metadata when present.
Validation rejects duplicate workflow field names before any browser work.
Validation rejects reserved or unsafe workflow field names before any browser work.
Validation rejects unsupported workflow field types before any browser work.
Validation rejects missing or non-http workflow target URLs before any browser work.
Invalid workflow names return invalid_workflow_name in JSON mode.
Missing workflows return workflow_not_found in JSON mode.
Unreadable workflows return workflow_unreadable in JSON mode for inspect and submit.
Invalid workflows return workflow_invalid in JSON mode for inspect and submit.
Create A New Workflow
Use record only when you need to create a workflow that does not exist yet.
formctl record expense-report https://example.internal/expense
formctl record expense-report https://example.internal/expense --manual
formctl record expense-report https://example.internal/expense --storage-state ./storage-state.json --headlessUse --manual when login, navigation, or form setup needs a human-visible browser before saving selectors.
Use --storage-state <path> with record or submit only after the user has completed login, MFA, or setup in a local browser session.
Manual recording stores redacted recording.events entries for changed fields and file inputs.
Commit or share the generated .formctl/workflows/<workflow-name>.yml file so other users can start from submit --dry-run.
record also saves a baseline screenshot next to the workflow file.
Commands
formctl submit <workflow-name> --dry-run [flags]
formctl submit <workflow-name> --approve [flags]
formctl submit <workflow-name> [flags]
formctl inspect <workflow-name> [--json]
formctl workflows [--json]
formctl validate <workflow-name> [--json]
formctl record <workflow-name> <url>
formctl doctor [--json]Browser mode defaults
recorddefaults to--headedso humans can watch login and form discovery.submit --dry-rundefaults to--headlessfor repeatable agent and CI previews.- Use
--headedor--headlessto override the default for any browser-backed command.
Workflow files are stored at:
.formctl/workflows/<workflow-name>.ymlSafety Contract
- Dry-run never clicks the recorded submit selector.
- Real submission requires
--approveor an interactive terminal confirmation. - Recorded selectors must match exactly one element.
- Missing or ambiguous selectors fail before filling fields or submitting.
Selector mismatch failures write
failure.json,failure.png, andaudit.jsonlwithout filling or submitting the form. - Login, CAPTCHA, and MFA walls fail before filling fields or submitting.
Interaction-required failures write
failure.json,failure.png, andaudit.jsonlwithinteraction_required,captcha_required, ormfa_required. - File inputs are redacted as
[file]in summaries. - Audit logs are written for successful dry-run, approved, and selector-mismatch failed runs.
- JSON output is available for agent and automation callers.
Exit codes
0 success
1 user/input error
2 workflow not found
3 selector mismatch
4 dry-run failed
5 approval required
6 interaction required
10 unexpected runtime errorDry-run browser runtime failures return dry_run_failed in JSON mode with failure.json and audit.jsonl artifacts.
Login, CAPTCHA, and MFA walls return interaction_required, captcha_required, or mfa_required in JSON mode with failure artifacts.
Agent Usage
Agents should call submit --dry-run --json first, inspect the returned artifacts, and only use --approve when the user or policy explicitly allows submission.
See the Agent safety guide for Codex, Claude Code, Cursor, Copilot CLI, and other coding agents.
See Why browser agents need form-specific CLIs for JSON branching examples and the agent-specific rationale. See the MCP setup guide for local-checkout and npm-based client configuration.
MCP Server
formctl-mcp exposes the dry-run-safe parts of the CLI to MCP clients:
npx formctl-mcpTools:
formctl_doctorformctl_workflowsformctl_inspectformctl_validateformctl_submit_dry_run
The MCP server does not expose approved submit. Agents still need a human or policy-approved CLI call to run formctl submit ... --approve.
Approval-required JSON looks like:
{
"status": "error",
"workflow": "expense-report",
"exitCode": 5,
"submitted": false,
"requiresApproval": true,
"error": {
"code": "approval_required",
"message": "Approval required: run with --dry-run to preview or --approve to submit."
}
}Current Scope
This is an early MVP. It currently records named form fields and a submit selector from a live page. It detects common login, CAPTCHA, and MFA walls as safe stops, but does not bypass them. It does not yet implement full event-history recording, credential storage, hosted execution, or selector healing.
