# Getting started: a notification shell

The fastest way to understand shells is the shipping one:
[`spt-shell-notify`](https://github.com/SaberMage/spt-shell-notify) renders
agent commands and surfaced notifications as **native OS notifications**
(Windows toast / Linux `notify-send`). Its manifest plus one small binary are
the **only** glue to spt-core — no spt-core source, no SDK; the binary speaks
the public `spt api` surface and nothing else. This page installs it, drives
it, and reads its manifest as the template for your own shell.

## 1. Install and spawn it

```console
$ git clone https://github.com/SaberMage/spt-shell-notify
$ cd spt-shell-notify
$ cargo install --path .        # puts `notify-shell` on PATH
$ spt adapter add .             # validates + registers the manifest
ADAPTER_ADD:notify:Shell:Copy (registered)
$ spt shell spawn notify        # mints an instance (notify-1) and launches it
```

`spawn` **mints a new instance identity** — `notify-1` — and launches the
binary; it's the creation act, not an on/off switch. The first spawn asks for
approval once (the manifest sets `require_approval = "remembered"`), and the
grant persists.

## 2. Drive it from an agent

Two render paths, by design:

**Explicit command** — an owner agent drives a toast down the durable command
channel:

```console
$ spt shell cmd notify-1 notify "build finished" "all 139 tests green"
```

The resident binary drains its command frames (`spt api … poll --link`) and
renders. Commands are validated against the manifest's declared vocabulary —
a verb or arity outside `[shell.capabilities]` is refused before it ever
reaches the binary.

**Surfaced notification** — no agent in the loop:

```console
$ spt subnet notify "deploy window opens in 10 minutes"
```

A subnet-wide notification resolves to the node the user most recently
touched, and spt-core spawns the shell's `[session.notif]` template there —
a native toast on the machine you're actually at.

## 3. Read the manifest

The complete contract for this shell, annotated:

```toml
[adapter]
name = "notify"
kind = "shell"
version = "1.0.0"
min_spt_core_version = "1.0.0"

[shell]
# Broker-launched; the {link_token} is the binary's only credential.
spawn = "notify-shell --link {link_token} --id {id}"
# A display is node-local; discovery never offers it off-node.
broadcast = "same-node"
# Auto-online with the owner: the notification surface should be up
# whenever the user's endpoint is.
persistent = true
# First spawn asks once; the grant is remembered.
require_approval = "remembered"
pre_close = "closing"
close_timeout_ms = 2000
# Offline wake-watcher: reports wake (exit code 86) after a short settle.
wake_command = "notify-shell --wake"

# The whole command vocabulary: one verb, two positional args.
[shell.capabilities.notify]
args = ["title", "body"]

# The notif render seam: spt-core fills the {notif_*} keys and spawns this
# detached when a notification surfaces at an endpoint this shell serves.
[session.notif]
command = 'notify-shell --render-title "{notif_from}" --render-body "{notif_body}"'
detach = true
keys = ["notif_id", "notif_from", "notif_subnet", "notif_body"]
```

What the binary itself does (three modes, ~one file):

- **resident** (`--link …`): calls `api bind-shell --link <token>` to come
  online, then loops `api poll --link <token>` draining command frames and
  rendering them.
- **one-shot render** (`--render-title/--render-body`): the `[session.notif]`
  template — render and exit.
- **wake watcher** (`--wake`): run while the instance is offline; exiting
  with code 86 signals "wake me".

## 4. Make your own

A shell is worth building whenever agents should *drive* something —
a desktop widget, a robot, a lamp, a game character, a sensor feed:

1. Start from this manifest; change `name`, `spawn`, and the
   `[shell.capabilities]` vocabulary to your verbs.
2. Your binary needs exactly three behaviors: **bind** with the link token,
   **drain** commands (`api poll --link`, or declare `command_receipt =
   "http"`/`"stdin"` if those fit better), and optionally **emit** sensory
   payloads back (`api emit … --type <t> --link <token>` — declared in
   `[shell.sensory]`, delivered only to a *live* owner session: sensors
   report the present, never the past).
3. Pick lifecycle behavior: `persistent` for always-up surfaces,
   `ephemeral = true` for fire-and-forget ones, `wake_command` if the
   surface can wake its owner.
4. `spt adapter add .` and `spt shell spawn <name>`.

Field-by-field details: the [manifest reference](../harness-contract/manifest.md#shell-adapters-kind--shell);
the shell-side api calls: the [`spt api` reference](../harness-contract/api.md#shells).
