The plugin command line

cxagent plugin makes, checks, runs, packs and verifies a plugin — with no session and no model.

cxagent plugin new <name> --lang csharp|c [--dir <folder>]
cxagent plugin check <folder> [--json]
cxagent plugin call <folder> <tool> ['<json>'] [options]
cxagent plugin call <folder> --command <name> [arguments…] [--json]
cxagent plugin pack <folder> [--native <lib>=<rid> …] [--out <folder>] [--description t]
                    [--publisher t] [--license t] [--repository url] [--info file.json|file.yaml] [--json]
cxagent plugin verify <entry.json | plugins.json> [--https-only] [--json]

A folder here is what a user installs: one <name>.plugin.json and the library of the same stem beside it (.dll, .so or .dylib), plus whatever that library needs. How to use these while you test is in testing.md; the way to the catalog is in publishing.md.

cxagent plugin new <name> --lang csharp|c [--dir <folder>]

Writes a working plugin project from the template cxagent carries: a sidecar, one tool with a per-call gate, the plugin, a README and a test project (C: the C SDK in cxp/, a CMake build and a CTest). It prints the next commands to run.

The same C# template is a dotnet new template too: dotnet new install CxAgent.Plugins.Templates, then dotnet new cxagent-plugin -n <name>.

cxagent plugin check <folder> [--json]

Every load-time check, without a session:

Then it loads the plugin, as a session would, without starting it. That load compares what the code returns with the sidecar. A native plugin's load also covers the plugin host's handshake, describe and version.

It reports every problem, not the first, one line each, naming the field:

$ cxagent plugin check weather/
kind: "native", but weather.dll is a .NET assembly.
tools[0].name: 'read_file' is a built-in tool name; a plugin cannot take it.

--json gives {"ok": false, "problems": [{"field": "kind", "message": "…"}]}.

cxagent plugin call <folder> <tool> ['<json>'] [options]

The real lifecycle once: Load, Start, the gate, Invoke, Stop.

$ cxagent plugin call weather/ search '{"city":"Athens"}' --answer allow
progress 50% fetching
asks: look up the weather for Athens
answered: allow
{
  "success": true,
  "exitCode": 0,
  "errorMessage": null,
  "output": {
    "content": "21°C, clear in Athens"
  }
}

The gate is the session's own decision. If the call would ask the user, call prints the question. Without --answer it stops there, as a session waits at its prompt: stopped: a user would be asked — pass --answer allow or --answer deny. --answer deny refuses it the same way, and the tool never runs.

Reports print as they happen. Progress, log lines and streamed text appear while the call runs, and the result follows once it returns. A submit from the plugin's client prints as would start a turn: <goal>. Nothing reaches a model.

Option
'<json>'the tool's arguments; must be a JSON object. Default {}
--settings '<json>' / --settings-file fthe plugin's settings block, as in config.json (one or the other)
--cwd dthe folder the plugin works in. Default: the current one
--answer allow|denyhow the permission question is answered
--timeout sstop the call after s seconds; the plugin is then stopped and its processes reaped. Default 60
--command name [args…]run a command the plugin declares instead of a tool; everything after the name is its argument
--jsonone JSON object (below), and nothing printed live. Anywhere on the line — after --command name too

--json, for check and call

Every outcome is one object on stdout, so a script can always parse it:

outcomeobject
check{"ok": bool, "problems": [{"field", "message"}]}
a call{"gate": {"display", "alwaysAskable", "subject"} | null, "answer": "allow" | "deny" | null, "stoppedAtGate": bool, "reports": ["progress 50% half", "log [Info] working", "stream text"], "submits": [goal], "result": {"success", "exitCode", "errorMessage", "output"} | null}
a command{"message": string | null, "status": "reported" | "changed" | "refused"}
load failed{"error": "load", "problems": [{"field", "message"}]} — check's shape
timed out{"error": "timeout", "seconds": n}

A usage error (exit 2) writes only to stderr.

cxagent plugin pack <folder> [options]

Writes what a release ships: a zip per build, each zip's sha256, and <name>.catalog.json, the entry that describes them. It runs check first and writes nothing when check finds a problem — a zip of a plugin that will not load is something every user's install would refuse.

Option
--native <lib>=<rid>a native plugin's library for one platform, repeated per platform; the rid is one of linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64, win-arm64. Each makes its own <name>-<rid>.zip. A managed plugin needs none and makes one <name>.zip
--out <folder>where the zips and the entry go. Default: the current folder
--description, --publisher, --license, --repositorythe entry's text the sidecar does not hold
--info file.json|file.yamlthe same four, from a file
--json{"zips": [{"file", "sha256", "platform"}], "entry": path, "unfilled": [field]} instead of the summary

Each text field comes from its option, else the --info file, else the project's Description, Authors, PackageLicenseExpression and RepositoryUrl. One still unknown is written as <fill in: …> and listed under "still to fill in"; verify refuses it until you do. Each zip's url is left as <upload URL>, and the summary ends with the steps left: upload, fill in the URLs, verify, submit. A plugin with no README.md beside it is packed anyway, with a note: the plugin manager shows the catalog description instead.

cxagent plugin verify <entry.json | plugins.json> [--https-only] [--json]

Checks what a user's install would get. For each entry it downloads every zip, checks its sha256, unpacks it and holds the sidecar and files up against every field the entry claims. No plugin code runs. Given the whole catalog, it checks every entry.

Exit codes

Every cxagent plugin command shares them: