Optional file conventions
Author CLI commands and Agent Tools in Rust or TypeScript while keeping optional dependencies isolated.
Requires the local Engine-enabled CLI and matching support packages described in Local App development. The examples here are implemented locally; they are not a registry release announcement.
A file takes effect when support is selected
An application selects a support Plugin; that Plugin supplies filename matching
and lowering. Other Plugins can include cli.rs, Console pages or Agent Tool
entries without forcing every consumer to adopt those surfaces. Engine owns
generic processing; each support Plugin owns its interpretation.
A simple Plugin has one Cargo.toml or package.json. Heavy optional dependencies
need independently buildable surface packages declared through surfaces
metadata or a composite plugin.json. Unselected independent surfaces are not
parsed, installed, compiled or bundled. Dependencies already imported by the
core or included in its Cargo workspace remain in that build. File naming alone
cannot remove a dependency that the core already requires.
Generic Engine processors can handle other languages, but App runtime support still depends on the selected SDK, execution Adapter and Host admission.
CLI commands
lenso app create my-cli --cli
cd my-cli
lenso app dev -- hello --name Ada
# After stopping development:
lenso app build
lenso app start --from dist -- hello --name Ada
The scaffold includes app/local.hello/cli.ts:
import { command } from '@lenso/cli';
export default command({
name: 'hello',
description: 'Say hello',
args: { name: { type: 'string', default: 'world' } },
run({ args, output }) { output.text(`Hello, ${args.name}!`); },
});
For an existing App, lenso app add @lenso/cli adopts the CLI support bundled
with the executable. It is not a marketplace lookup. A bare App-local directory
can contain only cli.ts or cli.rs; reusable packages should keep explicit
identities. Generated path-based identities change when their paths change.
Rust cli.rs can use the selected support’s macro:
use lenso_cli_support::command;
/// Say hello from Rust
#[command(name = "hello-rust")]
async fn hello(#[arg(long, default = "world")] name: String)
-> anyhow::Result<String>
{
Ok(format!("Hello, {name}!"))
}
One annotated function generates the entry factory. Typed arguments use
FromStr; Option<T> is optional and bool is a flag. Rust source requires
Cargo. Programmatic command builders and the underlying terminal Provider
remain available. Exercise terminal Stream commands through the App, rather
than standalone Request-only plugin dev.
Agent Tool entries
After explicitly adopting matching Agent Tool convention support, a Plugin may
provide agent/tools.ts:
import { tool, tools } from '@lenso/agent-tool-sdk';
import * as schema from '@lenso/agent-tool-sdk/schema';
export default tools([
tool({
name: 'greet', description: 'Greet a person.',
input: schema.object({ name: schema.string() }), output: schema.string(),
}, ({ name }) => ({ ok: true, value: `Hello, ${name}!` })),
]);
The Agent repository owns packages/agent-tool-convention and executable
examples/app-tools / examples/app-tools-rust fixtures. Rust entries use
#[lenso_agent_tool_sdk::tool_provider] with #[tool] methods. Use the matching
fixture for its exact SDK dependencies and generated registration.
Compilation produces ordinary Tool providers. A consuming Agent must install and select the provider and explicitly grant tool policy. Discovery does not start an Agent, choose a Model or authorize execution. The local fixture proof covers a real Turn, allowed execution, denial before execution, and provider removal. It currently relies on a local Bun Adapter fix; public installation requires released dependencies containing that fix.
For browser surfaces and backend service adapters, continue with App Console. For an ordinary provider or consumer, keep using Plugin authoring.