Plugin platform

Manifest reference

Every plugin.toml field, type and accepted value, checked against the manifest parser in lla 0.6.6.

↓ on this page

plugin.toml declares a plugin's identity, listing behavior, permissions and actions. It sits next to Cargo.toml in the source and at the root of the installed package.

lla validates the same rules three times: export_plugin! and export_component! at compile time, lla install, and package discovery when a plugin loads. Unknown keys are ignored, so a misspelled optional key produces no error.

Complete example

This manifest compiles, installs and passes lla plugin doctor.

schema_version = 3
 
[plugin]
id = "dev.example.my-plugin"
name = "my_plugin"
version = "1.0.0"
api_min = 3
api_max = 3
runtime = "native"
entrypoint = "my_plugin"
description = "Inspect files"
license = "MIT"
repository = "https://github.com/example/my-plugin"
 
[capabilities]
decorates_entries = true
formats = ["default", "long"]
machine_output = true
 
[permissions]
filesystem = ["metadata:selection", "read:user-path"]
network = ["api.example.com"]
process = false
clipboard = false
open_url = false
 
[[fields]]
name = "score"
type = "integer"
sortable = true
filterable = true
description = "Score derived from file size"
 
[[actions]]
id = "inspect"
description = "Inspect one or more paths"
examples = ["lla plugin run my_plugin inspect -- README.md --depth 2"]
interactive = false
arguments = [
  { name = "paths", type = "path", description = "Paths to inspect", position = 0, required = true, repeatable = true },
  { name = "depth", type = "integer", description = "Levels to descend", option = "--depth", default = 1, min = 0, max = 8 },
  { name = "mode", type = "string", description = "Inspection mode", option = "--mode", choices = ["quick", "full"] },
  { name = "hidden", type = "boolean", description = "Include hidden files", option = "--hidden", default = false },
]
output = { type = "table", columns = [
  { name = "path", type = "path", description = "Inspected path" },
  { name = "bytes", type = "bytes", description = "Size in bytes" },
] }

Top level

KeyTypeRequiredAccepted values
schema_versionintegeryes3
[plugin]tableyesSee [plugin]
[capabilities]tablenoSee [capabilities]
[permissions]tablenoSee [permissions]
[[fields]]array of tablesnoSee [[fields]]
[[actions]]array of tablesnoSee [[actions]]

Several keys below are identifiers. An identifier is non-empty and contains only ASCII letters, digits, ., _ and -.

[plugin]

KeyTypeRequiredDefaultAccepted values
idstringyesIdentifier. The stable package identity. Use a reverse-domain name such as dev.example.my-plugin.
namestringyesIdentifier. The name used in lla plugin run, --enable-plugin and lla plugin info.
versionstringyesAny non-blank string. lla does not parse it as SemVer.
api_minintegeryesNon-zero.
api_maxintegeryesAt least api_min. The range must include 3 for lla 0.6.6 to load the plugin.
runtimestringnonativenative or wasm-component.
entrypointstringyesOne filename with no directory part.
descriptionstringnoemptyAny string. lla list-plugins and lla plugin info show it.
licensestringnoAny string.
repositorystringnoAny string.

entrypoint works differently per runtime:

RuntimeExampleWhat lla looks for
nativefile_scoreThe exact name, then libfile_score.so, libfile_score.dylib or file_score.dll for the platform, then the name plus the platform suffix.
wasm-componentfile_score.wasmThe exact file. A source install copies it from target/wasm32-wasip2/release/.

A source install finds native libraries in target/release by matching the Cargo package name with - replaced by _. Keep the three names aligned: package file-score, [lib] name = "file_score" and entrypoint = "file_score".

[capabilities]

KeyTypeDefaultEffect
formatsstring array[]The views that call the plugin. Entries must be non-empty, unpadded and unique.
decorates_entriesbooleanfalseParsed, not consulted by the 0.6.6 host. Bundled plugins set it to record intent.
machine_outputbooleanfalseParsed, not consulted by the 0.6.6 host. Bundled plugins set it to record intent.

Any string passes validation in formats, but the host only sends the values below. A view missing from formats never calls the plugin.

CommandFormat sent
lladefault
lla -l, lla -Tlong
lla -ttree
lla -ggrid
lla -Rrecursive
lla -Ggit
lla --timelinetimeline
lla -Ssizemap
lla -F --respect-gitignorefuzzy, decoration only. The fuzzy view never asks for formatted text.
lla --json, --ndjson, --csvThe format of any view flag in the same command, otherwise default.

The table view normalizes to long, so declaring table has no effect.

[permissions]

KeyTypeDefaultAccepted values
filesystemstring array[]The scopes below. Entries must be unique.
networkstring array[]Exact host names. ASCII letters, digits, . and - only. No *, URL, port, leading or trailing ., or ...
processbooleanfalsetrue is rejected for wasm-component.
clipboardbooleanfalseAllows the host's clipboard-write call.
open_urlbooleanfalseAllows the host's open-url call.

Native plugins run fully trusted, so these keys are declarations that lla plugin info displays. A WebAssembly component only gets what the user approved, and the scopes control what it can reach.

For a selected entry, lla preopens the entry's parent directory. With a scope marked Tree below, a selected directory is preopened itself. For a user path, meaning a path value in an action's arguments, lla preopens the path's parent directory. A path containing .. is rejected.

ScopeApplies toList directoriesChange directory entriesRead filesWrite filesTree
metadata:selectionselected entriesyes
metadata:treeselected entriesyesyes
read:selectionselected entriesyesyes
read:treeselected entriesyesyesyes
write:treeselected entriesyesyesyesyes
delete:selectionselected entriesyesyes
read:user-pathaction path argumentsyesyes
write:user-pathaction path argumentsyesyesyes
write:selected-destinationaction path argumentsyesyesyes
delete:quarantinenone

delete:quarantine passes validation and opens nothing.

Runtime network checks compare the request's host with each entry exactly, and the comparison is case-sensitive.

[[fields]]

KeyTypeRequiredDefaultAccepted values
namestringyesIdentifier. Unique across fields.
typestringyesstring, integer, float, boolean, bytes, timestamp or path.
sortablebooleannofalseDeclaration only. The 0.6.6 host does not read it.
filterablebooleannofalseDeclaration only. The 0.6.6 host does not read it.
descriptionstringnoemptyAny string.

If a plugin sets a display string for a declared field and no typed value, the host converts the string to the declared type. lla plugin doctor fails with field 'score' cannot be converted to its declared Integer type when it can't.

[[actions]]

KeyTypeRequiredDefaultAccepted values
idstringyesIdentifier. Unique across actions.
descriptionstringyesNon-blank.
examplesstring arrayno[]Non-empty, unpadded and unique.
interactivebooleannofalseWhen true, stdin and stdout must be a terminal and --output must be human.
argumentsarray of inline tablesno[]See Arguments.
outputinline tableno{ type = "none" }See Output.

Arguments

KeyTypeRequiredAccepted values
namestringyesIdentifier. Unique within the action.
typestringyesstring, integer, float, boolean or path.
descriptionstringyesNon-blank.
positionintegerone of position, optionZero-based. Unique within the action.
optionstringone of position, optionStarts with --, at least 3 characters. Unique within the action.
requiredbooleannoCan't be true together with default.
defaultvaluenoMust match type.
repeatablebooleannoThe plugin receives a list. Read it with paths or strings.
choicesvalue arraynoEach value must match type.
min, maxnumbernointeger and float arguments only. min can't exceed max.

default and choices values match type as follows:

Argument typeTOML value
string, pathstring
integerinteger
floatinteger or float
booleanboolean

The host parses action arguments against this schema before it calls the plugin:

TokenHow it parses
--name valueSets the argument with option = "--name".
--name=valueThe same, inline.
--flagSets a boolean option to true. Use --flag=false to clear it.
Anything elseThe next positional argument. A single leading dash doesn't make a token an option.

A repeatable positional collects every remaining positional token, and a repeatable option accepts the flag more than once. The host fills in default when you omit an argument, so the plugin receives it as if you had typed it.

Output

typeExtra keysSDK callhumanjsonndjsoncsv
noneresponse::none()NothingnullnullError
textresponse::text(...)The textJSON stringJSON stringError
valueoptional schemaresponse::value(...)Pretty JSONPretty JSONOne line, or one line per item for a listError
tablerequired columnsresponse::table(...)Tab-separated with a headerArray of objectsOne object per lineHeader and rows

The host doesn't check the contents of schema against the returned value. It does check that the returned output kind matches the declared type, and fails with action 'inspect' returned output that does not match plugin.toml when it doesn't.

Each entry in columns:

KeyTypeRequiredAccepted values
namestringyesIdentifier. Unique within the table.
typestringyesThe field types from [[fields]].
descriptionstringnoAny string.

Table rows are checked strictly. Column names must match in order, each row needs one cell per column, and each cell's type must match exactly, so a bytes column needs value::bytes. A violation fails with action 'inspect' returned a table that violates its column schema.

Package integrity

The installer writes checksums.toml into each package. You don't write it by hand.

[files]
"libfile_score.dylib" = "<64 hex characters>"
"plugin.toml" = "<64 hex characters>"

The table must cover the entrypoint and plugin.toml. Paths are relative and name regular files. Symlinks are rejected. Each value is a SHA-256 digest of 64 hex characters.

Validation errors

ErrorFix
unsupported plugin manifest schema 2; expected 3Set schema_version = 3.
plugin.id must contain only ASCII letters, numbers, '.', '_' or '-'Remove spaces and other characters. The same error appears for plugin.name, field names, action IDs and argument names.
plugin API range is invalidSet api_min to at least 1 and api_max to at least api_min.
plugin.entrypoint must be a single package-local file nameRemove directories from entrypoint.
unknown filesystem permission scope 'read:all'Use a scope from the permissions table.
invalid network permission domain '*.example.com'List exact host names.
WASM plugins cannot request process permission in API v3Remove process = true, or use the native runtime.
action 'inspect' needs a descriptionAdd description.
argument 'path' in action 'inspect' needs a descriptionAdd description to the argument.
argument 'path' in action 'inspect' needs position or optionAdd one of the two.
required argument 'path' in action 'inspect' cannot have a defaultRemove required or default.
argument 'path' in action 'inspect' contains a value with the wrong typeMatch default and choices to type.
argument 'path' in action 'inspect' uses a numeric range with a non-numeric typeRemove min and max, or change type.
table output for action 'inspect' needs at least one columnAdd columns.