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.

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.

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.