Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# AGENTS.md — plugin-cua

Standalone out-of-tree plugin repo serving the `cua` computer-use verb
(`verb:cua`) and the `cua` device entity (`kind:cua`). The plugin is a Go module
at `candy/plugin-cua/` (module path
`github.com/opencharly/plugin-cua/candy/plugin-cua`); the root `charly.yml` only
declares `discover: candy` so the repo is a project and its candy is scanned.

Canonical files:

- `candy/plugin-cua/charly.yml` — the `plugin-cua:` candy entity (`plugin:`
block, `plan:` checks).
- `candy/plugin-cua/plugin.go` / `provider.go` — the verb provider, `NewMeta()`,
and the `Invoke` verdict path.
- `candy/plugin-cua/catalog.go` — one typed wrapper per method; `client.go` —
the `cua-driver` CLI client.
- `candy/plugin-cua/kind.go` — the `kind: cua` entity decode + device resolution.
- `candy/plugin-cua/schema/cua.cue` — the self-contained `#CuaInput` /
`#CuaMethod` / `#CuaDeviceInput` (single source for `params/cue_types_gen.go`).
- `charly.yml` — the root project manifest (`discover: candy`) plus the
disposable `cua-verb-probe` / `cua-entity-probe` local beds.
- `.github/workflows/tag-on-merge.yml` — CalVer tag + `CHANGELOG/` on merge;
`.github/workflows/ci.yml` — the module's Go gate.
- `README.md` — user overview only; never agent guidance.

## Load these skills first (R0)

- `/charly-check:cua` — the `cua:` verb + `kind: cua` reference. Load before
changing the verb surface or the device entity. This candy carries no
`skill:` entity of its own; the gap is tracked in
[opencharly/opencharly#291](https://github.com/opencharly/opencharly/issues/291).
- `/charly-internals:plugin` — the plugin authoring reference: the `plugin:`
block, the `verb` + `kind` provider classes, the per-plugin CUE-schema
contract.
- `/charly-internals:git-workflow` — before any git/PR action.

## Build / validate / test

- `go build ./...` in `candy/plugin-cua/` — compile the plugin module.
- `go test ./...` in `candy/plugin-cua/` — the plugin's Go tests.
- `charly box validate` at the repo root — the structural check (the candy +
`plugin:` block, CUE schema).
- The merge gate is the **org-wide** `charly/pr-validator` (required check
`validate / validate`, defined in `opencharly/.github`); this repo has **no**
per-repo candy gate. The `ci.yml` Go gate is this repo's own module test run.
- The R10 consumers are the `check-cua-*` beds in `opencharly/distro-omarchy`,
which compose this plugin against a live Cua Fleet desktop.

## Modify this repo

- Edit the `plugin-cua:` candy entity, the Go source, and `schema/cua.cue`
**together** — the schema is the single source for the verb's `params/` struct;
regenerate `params/cue_types_gen.go` from it.
- The read-only / mutating split is an ALLOWLIST (`catalog.go`'s
`mutatingMethods`): a new method is read-only by default until deliberately
classified. Keep the `allow_control` gate on every mutating path.

## Landing

- PR-only. Every change lands through a pull request; the org-required
`charly/pr-validator` validates the diff and body and arms native auto-merge on
PASS. Direct pushes to `main` are blocked.
- History lives in `CHANGELOG/` (written by `tag-on-merge` at merge time); the PR
body IS the changelog.
- The authoritative rulebook is the umbrella `AGENTS.md` in
`opencharly/opencharly` and `charly/AGENTS.md` in the charly repo. Do not
restate its rules here.
140 changes: 74 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,73 @@
# plugin-cua

The OpenCharly plugin serving the **`cua`** computer-use verb and the **`kind: cua`**
entity — driving a live desktop through [Cua Driver](https://github.com/trycua/cua) from
a charly candy/box plan, out-of-process.
The `cua:` computer-use verb and the `kind: cua` entity for OpenCharly — drive a
live desktop through [Cua Driver](https://github.com/trycua/cua) from a candy/box
plan, served out-of-process (`verb:cua`, `kind:cua`).

## What it is
The plugin is a standalone Go module the charly loader host-builds and serves
over go-plugin gRPC, so the Cua wire lives here, out of charly's core check
surface. Every call is routed through the venue's executor, so the driver is
driven **where it lives** (a desktop VM or pod), never on the host running
charly.

`cua:` is a DECLARATIVE check/control verb — authored as `cua: <method>` inside a
candy/box plan `check:`/`run:` step. It is NOT a host `charly check` subcommand: the
verb's implementation lives in this out-of-tree module, and at check time the host
dispatches `cua:` through the provider registry to this plugin (the same path `jetkvm:` /
`wl:` / `record:` take).
## What it provides

Cua Driver is the background computer-use driver that exposes its tools over MCP-over-stdio
**and** a CLI (`cua-driver call <tool> '<json>'`, `cua-driver status`, `cua-driver
list-tools`, …). This plugin uses the CLI: one process per call, no long-lived client
state — exactly what an out-of-process check step wants. Every call is routed through the
venue's executor, so the driver is driven **where it lives** (a desktop VM or pod), never on
the host running charly.
| Capability | Surface |
|---|---|
| `verb:cua` | the declarative `cua:` check/control step |
| `kind:cua` | the `cua:` device entity — connection + permission defaults a step inherits via `device:` |

## Authoring a `cua:` step
## The verb

The method name is the scalar value for a bare-method step (`cua: status`), or the
`method:` key of the `cua:` map when the step carries cua-exclusive fields — those live
INSIDE the `cua:` map. Only the shared matchers (`stdout:`, `stderr:`, `exit_status:`) and
`context:`/`id:`/`timeout:` stay siblings.
Cua Driver exposes its tools over MCP-over-stdio **and** a CLI
(`cua-driver call <tool> '<json>'`, `cua-driver status`, …). This plugin uses the
CLI: one process per call, no long-lived client state.

```yaml
- check: the Cua driver is running in the desktop session
context: [runtime]
cua: status
stdout:
- contains: running
Read-only: `status`, `doctor`, `version`, `list-tools`, `list-apps`,
`list-windows`, `get-window-state`, `get-desktop-state`, `get-screen-size`,
`screenshot`.

- check: a fresh desktop frame is captured as a real PNG
context: [runtime]
cua:
method: get-desktop-state
artifact: /tmp/desktop.png
artifact_min_bytes: 10000
artifact_not_uniform: true
```
Mutating (need `allow_control: true`): `click`, `double-click`, `right-click`,
`drag`, `type`, `press-key`, `hotkey`, `scroll`, `move-cursor`, `launch-app`,
`kill-app`, `bring-to-front`, `set-window-frame`, `set-value`, `recording-start`,
`recording-stop`, `recording-status`, `recording-render`, `call`.

`delivery: foreground` escalates an input action to Cua's foreground route; the
default is background, and a structured refusal (`background_unavailable`) is
never a silent success.

## READ-ONLY BY DEFAULT — the desktop-safety gate

A live desktop is not a throwaway. Every **mutating** method (input, app/window control,
recording, the raw `call` escape hatch) requires `allow_control: true`; without it the step
reports a documented skip naming the gate rather than acting. The classification is an
allowlist, so a method added later is read-only by default.
A live desktop is not a throwaway. Every **mutating** method requires
`allow_control: true`; without it the step reports a documented skip naming the
gate rather than acting. The classification is an allowlist, so a method added
later is read-only by default. Cua Driver's own permission mode (`standard` /
`bounded` / `unrestricted`), declared on the `kind: cua` entity, remains the
authority the plugin passes through.

Cua Driver's own permission mode (`standard` / `bounded` / `unrestricted`), declared on the
`kind: cua` entity, remains the authority the plugin passes through.
## How to use it

## Methods
Compose the plugin candy in a box or check bed's `candy:` list:

Read-only: `status`, `doctor`, `version`, `list-tools`, `list-apps`, `list-windows`,
`get-window-state`, `get-desktop-state`, `get-screen-size`, `screenshot`.
```yaml
- '@github.com/opencharly/plugin-cua/candy/plugin-cua:<tag>'
```

Mutating (need `allow_control: true`): `click`, `double-click`, `right-click`, `drag`,
`type`, `press-key`, `hotkey`, `scroll`, `move-cursor`, `launch-app`, `kill-app`,
`bring-to-front`, `set-window-frame`, `set-value`, `recording-start`, `recording-stop`,
`recording-status`, `recording-render`, `call`.
Then author the verb in a plan:

`delivery: foreground` escalates an input action to Cua's foreground route; the default is
background, and a structured refusal (`background_unavailable`) is never a silent success.
```yaml
- check: the Cua driver is running in the desktop session
context: [runtime]
cua: status
stdout:
- contains: running
```

## `kind: cua`
## The `kind: cua` entity

A `kind: cua` entity carries the connection/permission defaults a step inherits when it
names the entity with `device:` — the driver path, host, permission mode, and a default
session `env`. Authored step fields win over the entity's.
A `kind: cua` entity carries the connection/permission defaults a step inherits
when it names the entity with `device:` — the driver path, host, permission mode,
and a default session `env`. Authored step fields win over the entity's.

```yaml
my-desktop:
Expand All @@ -82,16 +80,26 @@ my-desktop:
WAYLAND_DISPLAY: wayland-1
```

## Verifying

```bash
charly check live <image> --filter cua # against a live deployment with the driver
charly check run cua-verb-probe # the registry/verdict smoke bed
```

The full contract is exercised by the `check-cua-*` beds in `opencharly/distro-omarchy`.

## License

MIT — see `LICENSE`. The plugin itself carries no Cua code; it drives the `cua-driver`
binary installed by the `layer-cua` candies.
## Layout

- `candy/plugin-cua/` — the plugin module: `plugin.go`, `provider.go`,
`catalog.go` (one wrapper per method), `client.go` (the `cua-driver` CLI
client), `kind.go` (the `kind: cua` entity decode + device resolution),
`schema/cua.cue` (the self-contained `#CuaInput` / `#CuaMethod` /
`#CuaDeviceInput`), `params/cue_types_gen.go`, `plugin_test.go`,
`cmd/serve/main.go`.
- `charly.yml` — the root project manifest (`discover: candy`) plus the
`cua-verb-probe` / `cua-entity-probe` disposable local beds.
- `.github/workflows/tag-on-merge.yml` — CalVer tag + `CHANGELOG/` on merge.
- `.github/workflows/ci.yml` — the Go gate (gofmt, golangci-lint, `go vet`,
`go test`) for the module's unit tests.
- `LICENSE` — MIT.

## Related

- Owning skill: `/charly-check:cua` — the `cua:` verb + `kind: cua` reference.
This candy carries no `skill:` entity of its own; the gap is tracked in
[opencharly/opencharly#291](https://github.com/opencharly/opencharly/issues/291).
- `/charly-check:check` — the declarative check-step surface the verb is
authored through.
- [`opencharly/charly`](https://github.com/opencharly/charly) — the charly CLI.
Loading