# Rhai script recipe

Programmable Guest is **Rhai only**. There is no WASM Component, WASI, or JS runtime.

`plugin.json`:

```json
"permissions": ["selection_read", "script"],
"entrypoint": { "kind": "script", "path": "plugin.rhai" },
"actions": [{
  "id": "run",
  "label": "Run",
  "handler": "script",
  "action": { "type": "noop" }
}]
```

Required exports in `plugin.rhai`:

```rhai
fn prepare(ctx) {
    [#{ actionId: "run", label: "Run" }]
}

fn execute(action_id, ctx) {
    plugin_text(ctx.text, "preview")
}
```

Host functions: `option_get` `secret_get` `state_get`/`state_set`/`state_delete` `progress` `network_request` `log` `url_encode` `parse_json` `to_json` `plugin_text` `plugin_fail`.

Rhai semantics to know (each one caused an official-pack hotfix):

- `parse_json` returns **object maps**: `value[key]` usually yields `()`. Read fields by property access, e.g. `payload.choices[0].message.content`.
- `trim()` / `replace()` **mutate in place and return `()`**: `text = text.trim()` empties the string. Mutate first, then use the variable, or write local helpers (copy `trim_string` / `replace_string` from the official pack).
- Missing context fields arrive as `()`, never as strings — test `ctx.html != ()` / `ctx.paths.len > 0` before use.
- Top-level `const` / `let` run into the scope **before** `prepare` / `execute`; safe as module constants.

`prepare` has no network. `execute` default 3 s; network execute ~20 s. Results are Host JSON. Do not open windows.

Official AI reference: `plugins/velo.ai-tools.vplug/plugin.rhai`.
