WordPress abilities pack that extends Dolly, the WordPress.com AI agent, with new capabilities. Abilities run remotely on self-hosted WordPress sites through the Jetpack connection.
- WordPress 6.9+
- Jetpack connected to WordPress.com
Releases publish four installable plugin ZIPs:
| Plugin | Contents | Dependency |
|---|---|---|
dollypack-core |
Shared runtime, settings UI, wp-remote-request, and edit-custom-ability. |
None |
dollypack-github |
All GitHub abilities plus the GitHub service parent class. | dollypack-core |
dollypack-google |
Google Calendar read plus the Google service parent class. | dollypack-core |
dollypack-full |
Core + GitHub + Google in one standalone plugin. | None |
Release assets are built from config/packages.php by scripts/build-packages.php. The repository root plugin mirrors the full bundle for development only and should not be installed alongside any packaged Dollypack plugin.
- Download the plugin ZIP you want from the GitHub Releases page.
- Choose the package that matches your setup:
dollypack-fullfor a single plugin with all Dollypack abilities.dollypack-corefor the shared runtime,wp-remote-request, andedit-custom-ability.dollypack-githubfor GitHub abilities, which requiresdollypack-core.dollypack-googlefor Google Calendar abilities, which requiresdollypack-core.
- In WordPress admin, go to Plugins > Add New Plugin > Upload Plugin and upload the ZIP file.
- Activate the plugin after upload. If you are installing
dollypack-githubordollypack-google, install and activatedollypack-corefirst. - Confirm Jetpack is connected to WordPress.com, then configure Dollypack under Settings > Dollypack.
| Ability ID | Package | Class | Description | Annotations |
|---|---|---|---|---|
wp-remote-request |
dollypack-core |
Dollypack_WP_Remote_Request |
Perform an HTTP request using wp_remote_request(). |
idempotent |
edit-custom-ability |
dollypack-core |
Dollypack_Edit_Custom_Ability |
Create, read, test, turn on, and turn off constrained custom abilities generated by Dollypack. | destructive |
github-read |
dollypack-github |
Dollypack_GitHub_Read |
Read files, directory listings, and repository metadata from the GitHub API. | readonly, idempotent |
github-notifications |
dollypack-github |
Dollypack_GitHub_Notifications |
List and manage GitHub notifications (list, mark-read). | idempotent |
github-search |
dollypack-github |
Dollypack_GitHub_Search |
Search GitHub for code, issues, repositories, or commits. | readonly, idempotent |
github-write |
dollypack-github |
Dollypack_GitHub_Write |
Create or update resources on GitHub — issues, comments, pull requests, etc. | destructive |
google-calendar-read |
dollypack-google |
Dollypack_Google_Calendar_Read |
Read calendars and events from Google Calendar (list_calendars, list_events, get_event). | readonly, idempotent |
edit-custom-ability lets Dollypack generate constrained custom abilities without registering arbitrary PHP immediately. The payload is limited to metadata, schemas, and the body of execute(). Dollypack generates the class file itself under custom-abilities/, tests it through a loopback request, and only registers it after a passing test.
- Generated abilities use runtime IDs like
custom-my-abilityand names likedollypack/custom-my-ability. - Generated ability state lives in the
dollypack_custom_abilitiesoption; the standard enabled-abilities option is not used as a second source of truth for them. - Stored registry entries are intentionally small and keep only the generated payload, lifecycle state, last test result, and any quarantine reason.
- A generated ability must pass
testbeforeturn_on. - If the generated PHP file changes after the last passing test, Dollypack quarantines it instead of registering it.
readreturns the generated file path and, when available, a WordPress plugin editor URL for manual inspection. Manual edits bypass the normal write flow, so they should always be followed bytestandturn_onagain.- WordPress admin shows a dedicated Custom Abilities section under Settings > Dollypack with state, last test status, surfaced issues, and
Test/Turn On/Turn Offactions.
Create a file in abilities/ with a class extending Dollypack_Ability (or a service-specific parent like Dollypack_GitHub_Ability).
Dollypack_Ability (abstract)
├── Dollypack_WP_Remote_Request
├── Dollypack_GitHub_Ability (abstract, shared $settings + github_request())
│ ├── Dollypack_GitHub_Read
│ ├── Dollypack_GitHub_Notifications
│ ├── Dollypack_GitHub_Search
│ └── Dollypack_GitHub_Write
└── Dollypack_Google_Ability (abstract, OAuth 2.0 + google_request())
└── Dollypack_Google_Calendar_Read
execute( $input )— performs the action and returns a result array orWP_Error.get_input_schema()— returns a JSON Schema array describing accepted input.get_output_schema()— returns a JSON Schema array describing the output.get_meta()— returns metadata includingannotations(readonly,destructive,idempotent) andshow_in_rest.
Register the ability with Dollypack_Runtime in the relevant package bootstrap:
packages/core/bootstrap.phpfor core abilitiespackages/github/bootstrap.phpfor GitHub abilitiespackages/google/bootstrap.phpfor Google abilitiespackages/full/bootstrap.phpfor the standalone full bundle
Use the plugin directory provided by the entrypoint, which is resolved with plugin_dir_path( __FILE__ ).
Dollypack_Runtime::register_ability(
'my-ability',
array(
'file' => $dollypack_plugin_dir . 'abilities/my-ability.php',
'class' => 'Dollypack_My_Ability',
)
);Update config/packages.php so the ability is bundled into the correct plugin ZIPs.
Add a row to the abilities table above and note which package now contains it.
Abilities declare settings as a $settings array on the class:
protected $settings = array(
'github_token' => array(
'type' => 'password',
'name' => 'GitHub Token',
'label' => 'Personal access token for the GitHub API.',
'storage' => 'user',
'encrypted' => true,
),
);- Inheritance: Settings declared on a parent class (e.g.
Dollypack_GitHub_Ability) are shared by all children. The storage key is prefixed with the declaring class's$id, so all GitHub abilities share a single_dollypack_github_github_tokenuser-meta key. $group_label: Set on a parent class to group its children under one heading in the admin UI (e.g.'GitHub').storage: Optional. Use'site'for WordPress options or'user'for user meta. Defaults to'site'.encrypted: Optional. Whentrue, Dollypack encrypts the stored value before writing it to the database and decrypts it on read. Use this for tokens, client secrets, and similar credentials.- Storage key naming: Site options use
dollypack_{declaring_class_id}_{setting_id}. User meta uses_{same_key}so it is treated as protected meta by WordPress conventions.
When adding a new service (e.g. Slack), create an abstract parent in includes/:
- Extend
Dollypack_Ability. - Set a shared
$id(e.g.'slack'),$group_label, and$settingsfor credentials. - Add a helper method for authenticated API requests (like
github_request()). - Load the file from the relevant bootstrap(s).
- Add the parent class file to the appropriate module in
config/packages.php. - Have each concrete ability extend this parent.
For services requiring OAuth 2.0 (e.g. Google), the parent class handles the full authorization code flow:
$settingsdeclares site-scoped app credentials likeclient_idandclient_secret— rendered as standard inputs by the settings page.render_settings_html()— static method that outputs extra<tr>rows in the settings form (Connect/Disconnect buttons, connection status). The settings page calls this automatically if the method exists on the parent class.handle_oauth_callback()andhandle_disconnect()— registered asadmin_post_hooks in the constructor (with a static flag to avoid duplicate registration).has_required_settings()is overridden to also check that a refresh token exists, so abilities stay disabled until the OAuth flow completes.- Token storage — access token and refresh token are stored per user in encrypted
user_meta; expiry remains plain because it is not secret. Theget_access_token()method auto-refreshes when the token is expired or expiring within 60 seconds. - Key material — encryption keys are derived from WordPress salts via
wp_salt(). If those salts are rotated, stored secrets can no longer be decrypted and must be re-entered or re-authorized.
These rules apply when creating or modifying abilities:
- Each ability = a permission level. Abilities are individually toggleable in the admin UI (Settings > Dollypack). Think of each one as a trust boundary.
- Prefer fewer, broader abilities over many narrow ones. Combine endpoints that share the same trust level into one ability (e.g. all read-only GitHub API calls go into
github-read). - Split when risk differs. Separate read from write, or when a user would reasonably want one without the other (e.g.
github-readvsgithub-write). - Use enums to constrain actions. When a single ability supports multiple operations, use an
enumin the input schema (e.g.github-notificationshasaction: ['list', 'mark-read']). - Mark annotations accurately. Set
readonly,destructive, andidempotentto reflect what the ability actually does. These inform the agent's decision-making. - Keep this file up to date. This README is also
CLAUDE.mdandAGENTS.md. When you add or change an ability, update the abilities table and any relevant sections.
# Build all release ZIPs into dist/
php scripts/build-packages.php --all
# Build with a specific version injected into plugin headers
php scripts/build-packages.php --all --version 1.2.0
# Build a single package
php scripts/build-packages.php dollypack-core- Package composition lives in
config/packages.php, which defines modules (file groups) and packages (collections of modules). Module entries can be simple strings (source = destination) or arrays with explicitsource/destinationmappings for files that relocate into the package root (like bootstraps). - The build script resolves modules into file lists, copies them into
dist/{package}/, injects the version into the plugin header, runsphp -lon every PHP file, and creates the ZIP. - GitHub Actions publishes
dollypack-core.zip,dollypack-github.zip,dollypack-google.zip, anddollypack-full.zipas release assets forv*tags, and injects the tag version into each plugin header. - Add-on plugins include a fallback admin notice with the releases URL if
dollypack-coreis missing. - There is no test suite, linter config, or dependency manager. The only automated check is
php -lsyntax linting during the build.
The root dollypack.php is the development entrypoint — it acts as a full bundle and must not be installed alongside any packaged plugin. It loads Dollypack_Package_Helper to detect conflicts with packaged plugins, then delegates to packages/full/bootstrap.php.
Each package has its own entrypoint (packages/{name}/dollypack-{name}.php) and bootstrap (packages/{name}/bootstrap.php). The core and full bootstraps load the runtime and register abilities directly. Add-on bootstraps (github, google) defer registration to plugins_loaded at priority 20 and call Dollypack_Package_Helper::ensure_core_runtime() to verify core is active first.
Dollypack_Runtime is a static singleton that manages the ability lifecycle:
- Abilities are registered via
register_ability($id, ['file' => ..., 'class' => ...])— class files are lazy-loaded on first instantiation. - On
wp_abilities_api_categories_init, it registers thedollypackcategory. - On
wp_abilities_api_init, it instantiates all registered abilities, checks which are enabled via thedollypack_enabled_abilitiesoption, verifies required settings are populated, and callsregister()on each qualifying ability.