Skip to content

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:

sh
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-sdk

The 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:

TemplateWhat it implements
custom (default)A custom service with echo and a cancellable wait
filesA read-only file service over a synthetic inventory
consoleA console service with sessions, reads and writes
settingsA 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.

rust
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 to failed.
  • Cancellation is cooperative: check context.is_canceled(), or await context.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

sh
shellcanvas-adapter build ./my-device ./my-device-package
shellcanvas-adapter validate ./my-device-package/adapter.json

build 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, which build expects.
  • 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.

ShellCanvas documentation