A tool is a program the Vitruvian app starts and talks to. This folder says
how they talk. It is the source of truth: the Swift types in
../Sources/VitruvianToolProtocol and the script in ../conformance are
written to match it, and tests fail when they stop matching.
| File | What it is |
|---|---|
manifest.schema.json |
vitruvian-tool.json, the file at the top of a tool’s folder |
messages.schema.json |
one message, and what each message carries |
methods.json |
every message: who sends it, whether it is answered, what it needs |
examples/ |
manifests and messages, valid and not, that both sides are tested against; and folders.json, folders the app and vitruvian-tool validate must judge alike |
Every field has a description in its schema. This page is the overview.
The link
The app starts the tool’s program in a folder the tool may keep files in. The program’s standard input and standard output are the link. Its standard error is its log.
- One message per line, in UTF-8. A line ends with a line break (
\n) and has none inside it. JSON never needs one: inside a string it is written\n. - A line is at most 1,048,576 bytes, not counting its line break.
- An empty line is ignored. Anything else that is not a message is an error.
- A last line with no line break is never delivered: the reader holds it until its line break comes, so end every message with one.
- Lines end with
\nalone. With\r\nthe\rstays in the line and counts toward the 1,048,576 bytes. - The tool’s standard output carries nothing but messages. Print to standard error instead.
A message
A message is JSON-RPC 2.0. There are four kinds.
{"jsonrpc":"2.0","id":2,"method":"command/run","params":{"command":"copyLatest"}}
{"jsonrpc":"2.0","method":"commands/state","params":{"runnable":["copyLatest"]}}
{"jsonrpc":"2.0","id":2,"result":{}}
{"jsonrpc":"2.0","id":2,"error":{"code":-32010,"message":"command refused"}}
- A request has an
idand is answered, with a result or a failure that carries the sameid. - A notice has no
idand is not answered. - An
idis a whole number or a string. Whoever sends the request picks it. paramsis always an object. A message that carries nothing leaves it out.- A request that was simply done is answered with
{}. - A field you do not know is ignored. That is how a later version adds one. A request you do not know is answered with failure -32601. A notice you do not know is ignored.
What the app sends
| Message | Kind | Meaning |
|---|---|---|
initialize |
request | The first message. It carries the protocol versions the app speaks, the language in use and the tool’s data folder. The tool answers with the version it picked and its own version. Until it has, neither side sends anything else. |
command/run |
request | Run this command. command is the id the manifest gives it. Answer {} when it ran, or failure -32010 when you will not run it. |
preferences/changed |
notice | The person changed these preferences, in the tool’s detail in Settings: each one’s id and its new value. Sent only to a tool that is running and has answered initialize. |
language/changed |
notice | The person changed the app’s language: the new one, as a tag. Sent only to a tool that is running and has answered initialize, once for each change. A tool that starts later learns the language from initialize. |
shutdown |
notice | Stop. The app then closes the tool’s input. The tool exits with 0 within two seconds. |
What a tool sends
A tool may do any of this itself. It is a separate program with everything the app has. These messages are for what is better done through the app.
| Message | Kind | In uses |
Meaning |
|---|---|---|---|
commands/state |
notice | Which commands can run now. Send it whenever that changes. Until the first one, all of them can. | |
notify/hud |
request | notify |
Show the app’s short on-screen message, with a symbol. |
notify/beep |
request | notify |
The alert sound. |
open/url |
request | open |
Open a link. |
clipboard/readText |
request | clipboard.read |
The clipboard’s text. |
clipboard/writeText |
request | clipboard.write |
Replace the clipboard with this text, marked as Vitruvian’s so clipboard history files it correctly. |
keystrokes/paste |
request | keystrokes |
Paste this text into the app in front. |
keystrokes/pressMenuItem |
request | keystrokes |
Press the menu item with one of these key equivalents in the app in front. |
processes/terminate |
request | processes |
End this process, with the app’s checks and its administrator prompt. |
storage/get, storage/set |
request | storage |
Read or write one of the preferences the manifest declares. Text is at most 4,096 characters; a longer one is refused with -32602. |
log |
notice | A line for the tool’s log in Settings. |
Being refused
Any request can be refused. A refusal is a failure with one of these codes.
| Code | Who | Meaning |
|---|---|---|
| -32001 | app | The service is not listed in the tool’s uses. data.service names it. |
| -32002 | app | The tool is not installed. |
| -32003 | app | Vitruvian lacks a macOS permission the service needs. data.permission names it. |
| -32004 | app | Not available right now. |
| -32010 | tool | The tool will not run the command. |
The other codes are JSON-RPC’s own: -32700 not JSON, -32600 not a message, -32601 unknown method, -32602 wrong params, -32603 something broke inside.
A failure’s message is a few English words for a log. It is never shown to
the person as it is.
The manifest
vitruvian-tool.json says what the tool is. id, name, version,
protocol, publisher and runtime are required.
- The id has a dot (
com.acme.deploys) and is at most 128 characters. - Text is a map of languages,
{ "en": "Deploys", "fr": "Déploiements" }. English is required. It is shown as written. useslists the app services the tool calls. It is not a list of what the tool is allowed to do.- A name the app does not know refuses the tool: a service, a surface, a kind of preference, a way of starting. The reason names it.
runtime.execstays inside the folder.- A preference may carry a
title, text in several languages like any other. It is what the setting is called to the person. One with no title is the tool’s alone, and the app never shows it. One with a title is listed in the tool’s detail in Settings, where the person can change it. The field was added after the first version 1, with no new version: an app from before it ignores it, and shows no setting. - Limits: the file 65,536 bytes, 32 commands, 16 preferences, a
preference’s title 60 characters in each language with no control
character, and text saved with
storage/set4,096 characters. A character here is a Unicode code point, as JSON Schema counts.
Changing the protocol
A new message or field needs no new version when an old tool or an old app can ignore it. Anything an old one would misread needs version 2.
A change is three things in one commit: the schema, the Swift type, and an
example in examples/. The tests hold the three together, and they can only
see a difference an example crosses.
Every message, from methods.json
| Message | Sent by | Kind | In uses |
|---|---|---|---|
initialize |
app | request | |
command/run |
app | request | |
preferences/changed |
app | notice | |
language/changed |
app | notice | |
shutdown |
app | notice | |
commands/state |
tool | notice | |
notify/hud |
tool | request | notify |
notify/beep |
tool | request | notify |
open/url |
tool | request | open |
clipboard/readText |
tool | request | clipboard.read |
clipboard/writeText |
tool | request | clipboard.write |
keystrokes/paste |
tool | request | keystrokes |
keystrokes/pressMenuItem |
tool | request | keystrokes |
processes/terminate |
tool | request | processes |
storage/get |
tool | request | storage |
storage/set |
tool | request | storage |
log |
tool | notice |