SDK and plugin creator
BetaWrengle's plugin SDK and in-app creator are for local developer-preview plugins. They do not imply marketplace availability, remote installation, or public API stability. Generated folders are trusted local code, and host access works only through grants made in this app install.
Creator templates
Open Settings → Plugins → Developer → Plugin creator to generate a local plugin folder.
The creator derives a plugin.<slug> id from the name, previews the manifest,
and shows the capability grants the template will request before files are
created.
| Template | Type | Generated files | Capability grants |
|---|---|---|---|
| Echo action | WASM action | Cargo.toml, plugin.toml, src/lib.rs, README.md | None by default |
| Note writer proposal | WASM action | Rust action scaffold using wrengle_plugin_sdk::host | noteRead, noteWrite |
| HTTP secret action | WASM action | Rust action scaffold using host::secret and host::http | secret:<alias>, httpHosts:<host> |
| View | View-only plugin | plugin.toml, ui/index.html, README.md | None by default |
You can also add explicit grant keys such as pluginState, blockRead, or
blockWrite before creating the scaffold. The creator writes those keys as
manifest [[capabilities]] declarations; the user still grants or denies them
later in Wrengle. Declarations are not grants. Plugin Builder is separate from
Plugin Creator and currently creates only static custom views and offline
feature apps with plugin-scoped JSON state. Builder-created apps do not receive
dashboardRead, dashboard snapshots, workspace readers, workspace writers,
network access, secrets, shell access, or generated WASM actions. Workspace-aware
generated plugins are future work.
SDK import example
Generated WASM action templates use the repository-local Rust SDK:
[dependencies]
extism-pdk = "1.4"
serde_json = "1"
wrengle-plugin-sdk = { path = "../../../crates/plugin-sdk" }The exact relative path depends on where the generated folder lives. If you move
the plugin folder, edit the wrengle-plugin-sdk path to point at
crates/plugin-sdk in your Wrengle checkout.
Use SDK DTOs and host helpers from wrengle_plugin_sdk:
use extism_pdk::{plugin_fn, FnResult, Json};
use serde_json::json;
use wrengle_plugin_sdk::{host, ActionResult, Error, NoteWriteRequest, RunEnvelope};
#[plugin_fn]
pub fn run_action(Json(envelope): Json<RunEnvelope>) -> FnResult<Json<ActionResult>> {
let RunEnvelope { action_id, request } = envelope;
let path = "notes/inbox/plugin-output.md";
let existing = match host::read_note(path) {
Ok(markdown) => markdown,
Err(Error::NotFound(_)) => String::new(),
Err(error) => return Err(extism_pdk::Error::msg(error.to_string())),
};
let result = host::write_note(NoteWriteRequest {
path: path.into(),
expected_revision: None,
base_content_hash: None,
new_markdown: format!("{}\n\n{}\n", existing.trim_end(), request.selection),
summary: "Append selected text from plugin".into(),
})
.map_err(|error| extism_pdk::Error::msg(error.to_string()))?;
Ok(Json(ActionResult {
summary: "Prepared note write proposal".into(),
reference: None,
data: json!({
"actionId": action_id,
"proposalId": result.proposal_id,
"status": result.status,
}),
}))
}Exact grant examples
Parameterized grants include their exact scope:
[[capabilities]]
scope = "httpHosts"
hosts = ["api.github.com", "api.example.com"]
[[capabilities]]
scope = "secret"
keys = ["github.token", "example.token"]Settings shows those as httpHosts:api.github.com,
httpHosts:api.example.com, secret:github.token, and
secret:example.token. The host bridge accepts only the exact granted HTTP
host, and secret calls can read only the exact granted alias.
Unparameterized grant keys are written directly:
[[capabilities]]
scope = "noteRead"
[[capabilities]]
scope = "noteWrite"
[[capabilities]]
scope = "blockWrite"
[[capabilities]]
scope = "pluginState"noteRead gates note reads, noteWrite gates note write proposals,
blockWrite gates block write proposals, and pluginState gates plugin-scoped
JSON state. dashboardRead is reserved for future workspace-aware plugin
surfaces; current Builder-created apps are offline and state-only.
readVault is accepted as a note-read capability for current host functions,
but new local plugins should prefer the narrower noteRead grant when they
only read note content.
Note and block host helpers only accept vault-relative Markdown note paths under
notes/, projects/, or people/. They reject absolute paths, whitespace
padding, ./.. traversal, hidden path segments, backslashes, empty segments,
non-.md files, and other vault roots before reading or proposing a write.
Host error handling
Host helpers return Result<T, wrengle_plugin_sdk::Error>. Missing grants,
denied proposals, invalid requests, stale write bases, and host failures are
not panics; handle them in plugin code.
fn update_counter() -> Result<ActionResult, String> {
match host::state_get("counter") {
Ok(result) => {
let value = result.value.unwrap_or_else(|| serde_json::json!(0));
host::state_set("counter", value).map_err(|error| error.to_string())?;
Ok(ActionResult::summary("Updated plugin state"))
}
Err(Error::CapabilityDenied(message)) => Ok(ActionResult::summary(format!(
"Missing pluginState grant: {message}"
))),
Err(error) => Err(error.to_string()),
}
}When a grant is missing, the host returns a capabilityDenied envelope and the
SDK maps it to Error::CapabilityDenied. Running without a grant does not
bypass the host API.
Write proposal flow
noteWrite and blockWrite do not let plugin code write directly. They let the
plugin ask Wrengle to prepare app-mediated write proposals:
- The plugin declares
noteWriteorblockWrite. - The user grants that capability for this local plugin install.
- The action calls
host::write_noteorhost::write_blocks. - The host returns
pendingUserApprovalwith a proposal id and preview data. - Wrengle shows the proposal in the action dialog.
- The user approves or denies the proposal.
- Approved note proposals re-check the current note before saving. Conflicts are reported instead of overwriting silently.
host::write_note sends a complete raw Markdown replacement candidate. Wrengle
previews and conflict-checks that candidate, but it does not reinterpret plugin
noteWrite payloads as structured block ops. Prefer host::write_blocks for
list, checklist, table, or other block-precise edits.
Block write proposals use the same approval shape. Approval re-reads the current note, checks the proposal hash and any expected revision, then applies the block edit through the vault save and commit path. Conflicts are reported instead of silently overwriting current note content. Use insert, update, and remove block operations for proposals. Move operations currently return a structured plugin error until native block reordering is wired into the shared block editor.
View network boundary
Manual/developer view-only plugins use local HTML and JavaScript inside a
sandboxed iframe. The view bridge currently sends theme and selected-text
messages only. Direct view JavaScript network calls from those manual views,
such as fetch and XMLHttpRequest, are not controlled by the httpHosts
grant. httpHosts gates only Wrengle host-bridge HTTP made by WASM actions.
Builder-created offline apps are also plugin views, but they are checked against
network access and remain offline.