Build your first adapter
An adapter is a small native program that supplies a workspace with files, a console, settings or your own custom services. ShellCanvas launches it, speaks a simple framed protocol over its standard input and output, and exposes whatever it advertises.
Prerequisites
- Rust 1.93 or newer, and a checkout of the ShellCanvas application repository — the generator points your project at the SDK source it finds there.
- ShellCanvas on Windows to install the result.
- An understanding of the trust involved: an adapter is native code that runs with your own operating-system permissions. It is not sandboxed.
The crate is shellcanvas-adapter-sdk, published on crates.io as a provisional 0.1 release.
Steps
1. Generate a project
Build the CLI from the application repository, then generate:
cargo build --bin shellcanvas-adapter --locked
shellcanvas-adapter init ./my-device \
--id example.device --name "My device" \
--sdk-source /absolute/path/to/ShellCanvas/crates/adapter-sdkThe identifier must be namespaced — at least one dot — and may not start with system. init writes into a new directory only, and produces Cargo.toml, src/main.rs, adapter.json, README.md and .gitignore.
Four templates are available with --template:
| Template | What it implements |
|---|---|
custom (default) | A custom service with echo and a cancellable wait |
files | A read-only file service over a synthetic inventory |
console | A console service with sessions, reads and writes |
settings | A host-settings service with one editable field |
The generated project depends on the SDK by path. To build against the published crate instead, replace that dependency with shellcanvas-adapter-sdk = "0.1".
2. Implement the adapter
Two methods carry everything: initialize returns what you offer, and call serves it.
use shellcanvas_adapter_sdk::{async_trait, json, run, Adapter, CallError, RequestContext, ServiceDescriptor, Value};
struct Device;
#[async_trait]
impl Adapter for Device {
async fn initialize(
&self,
_configuration: Value,
_context: RequestContext,
) -> Result<Vec<ServiceDescriptor>, CallError> {
Ok(vec![ServiceDescriptor {
id: "example.device".into(),
version: 1,
methods: vec!["example.device.read".into()],
}])
}
async fn call(
&self,
method: &str,
params: Value,
context: RequestContext,
) -> Result<Value, CallError> {
match method {
"example.device.read" => {
if context.is_canceled() {
return Err(CallError::new("aborted", "Read canceled"));
}
let probe = params.get("probe").and_then(Value::as_str).unwrap_or("default");
Ok(json!({ "probe": probe, "celsius": 21.5 }))
}
_ => Err(CallError::new("unavailable", "Unknown method")),
}
}
}
fn main() -> std::io::Result<()> {
run(Device)
}Rules the host enforces, so build to them from the start:
- Every method name must begin with its service id, and every service you advertise must be namespaced and versioned from 1.
- Error codes are a closed set:
invalid,closed,aborted,denied,unavailable,busy,failed,deadline. Anything else is normalised tofailed. - Cancellation is cooperative: check
context.is_canceled(), or awaitcontext.canceled(), in anything slow. - Standard output is the protocol. Never print to it. Standard error is discarded by the host, so log to your own file if you need logs.
3. Build a package
shellcanvas-adapter build ./my-device ./my-device-package
shellcanvas-adapter validate ./my-device-package/adapter.jsonbuild compiles a release binary and packs it with adapter.json, recording every file's size and SHA-256. validate re-checks those hashes without executing anything. Use --debug for a debug build while developing, and note that the output directory must not already exist.
4. Install it
In ShellCanvas, open App Manager → Connection adapters, choose Install adapter, select adapter.json from your package directory, and accept the native-code trust prompt. Then add it as a source in the connection editor and select which services it provides — files, console, remote settings — or find its custom services under Additional services.
Expected result
Opening a workspace with your adapter as a source shows exactly the capabilities it advertised: apps and built-in windows use them through the same service interfaces they use for SSH, and anything you did not advertise is simply unavailable rather than broken.
Limitations
- Trusted native code. Adapters are not sandboxed and run with your permissions. The desktop reviews identity and hashes; it does not verify a publisher.
- Process supervision is Windows-only. On Windows, adapters run in a job object so they cannot outlive the desktop. Equivalent supervision on other platforms is not implemented.
- The CLI parses arguments positionally. Flags must appear in the documented order; anything else is refused.
- Generated projects use a fixed binary name,
shellcanvas-device, whichbuildexpects. - The SDK is provisional at 0.1. Interfaces can change before 1.0.
- No production adapters ship with ShellCanvas. The templates are synthetic examples, not protocol implementations for real devices.
Related guides
- Adapter process protocol — the wire format and its rules.
- Adapter services — what to advertise for each desktop role.
- Adapter packages — the manifest, hashing and installation.
- Adapter diagnostics — what to do when it does not connect.