Plugin Protocol
A plugin is a program installed and managed by lnk. The core invokes it
through this protocol and links no plugin implementation. Its owner keeps
plugin.toml, its Cargo manifest, help, tests and documentation together.
Discovery
How lnk learns what a plugin is and what it documents, by asking the
plugin itself. Discovery and help work without installing operational
dependencies.
lnk-<id> --link-describe
Prints a versioned, self-contained JSON component descriptor. Cargo supplies its identity, executable, version, license and summary. The descriptor supplies its kind, command routes, typed options, help, required and recommended operations, install menu, removal metadata, canonical documentation topics, and whether their first page opens with a quickstart. Unknown schema versions and ambiguous executable identities are refused.
A command's help
A command's help describes its arguments and flags. lnk variable list <command> lists its declared environment inputs separately, including
option-backed inputs and those of the program and ancestor commands.
The list shows names and descriptions without reading current values.
lnk-<id> --link-documentation
Prints its embedded documentation pages. Each page has the same canonical path as its website page. Documentation and the runtime descriptor are embedded at build time; test providers, source pins and build inputs remain build metadata.
Adding and removing plugins
How lnk lists, adds and requires plugins, and what it does for one
that isn't installed.
lnk plugin
lnk plugin list --json prints link_plugin::plugins::Listed entries:
{"name", "installed", "source"?, "key"?, "answers"?}. An outside
plugin includes its repository and pinned signing key. lnk plugin add <url> --yes --key <key> installs its signed release, trusting only
that key.
A missing plugin
A missing plugin returns exit status 4
(link_plugin::plugins::NOT_INSTALLED) and explains how to install it.
Requirements and recommendations
Required operations propagate the called command's typed option bindings and platform predicates. Recommendations enter the closure only when selected. A missing applicable requirement blocks the operation.
Questions
A plugin learns what the plugins built on it have without naming them: it asks a question, and each installed plugin that answers it does.
A question
A question is link_plugin::answers::Question<I, O>, named
<plugin>.<what> for the plugin that asks it, and defined in that
plugin's crate or contract, so only a plugin built on it can depend on
its types. A plugin answers in its plugin.toml, each question by a
command of its own and the words after it, as answers = { "bucket.folders" = "agent answer bucket.folders" } does.
Question::ask
Question::ask runs lnk <command> for each installed plugin lnk plugin list --json lists as answering, writes the question's I as
JSON to its stdin, and reads its stdout as the question's O. With
none installed there is no answer, so a plugin above that isn't there
has nothing to say. The layers check refuses an answer to a question of
a layer the answering plugin isn't built on.
Shared discovery
Question::answerers_in selects typed answerers from a listing the caller
already obtained. Several questions can share one current listing without
starting another discovery command. The caller owns when it refreshes that
listing, and Answerer::reply retains the ordinary command protocol.
Uninstalling (lnk-<plugin> uninstall)
uninstall --parts --json prints link_plugin::uninstall::Kept:
{"parts": [{"name": "example", "about": "saved settings", "paths": [], "keys": [], "first": "", "secrets": [], "credentials": []}], "stops": "", "stays": ""}Each part
Each part declares absolute paths within the user's settings or data
folders and Keychain accounts, exact or a prefix ending in /. first
explains how to keep a copy. secrets selects paths containing private
state; omission means all paths. credentials contains Held entries
with what, optional of, kept and may, never secret values.
stops explains what stops, and stays what remains elsewhere.
lnk uninstall --parts --json
Returns installed plugins' declarations without deleting anything.
Interactive removal merges parts with one name; rest is data no
plugin claims. A plugin that does not answer has no declared state or
services.
uninstall [--delete <part>]...
Stops the plugin's services and removes only selected parts, using
link_plugin::uninstall::run. It returns 0 when complete. Removing a
plugin without selected parts stops its services and retains its data.
A failed removal retains the executable and reports its stderr.
Downloads
A download a plugin installs, such as a harness's release, is kept by
its SHA-256 and checked before it's used again. LNK_ARTIFACT_CACHE
chooses the shared cache, and LNK_ARTIFACT_BUDGET bounds it, 2 GiB by
default. Writers lock, publish whole and evict older downloads within
the budget. Nothing that changes, and no build output, is kept there.