From 6230916d4b8ee468a90b0016c5e42521830e6ab4 Mon Sep 17 00:00:00 2001 From: Drew Tada Date: Tue, 30 Jan 2024 13:04:50 -0500 Subject: [PATCH 1/4] piping and writing scripts --- src/cookbook/writing_scripts.md | 14 ++-- src/terminal.md | 129 +++++++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/cookbook/writing_scripts.md b/src/cookbook/writing_scripts.md index 188d9af4..0c8de4d1 100644 --- a/src/cookbook/writing_scripts.md +++ b/src/cookbook/writing_scripts.md @@ -9,10 +9,10 @@ When writing a script, you cannot control the `OnExit` behavior like you can wit ## Writing a Script Let's look at the simplest possible script: `echo`, which takes in an argument, and prints it out again: ```rust -use kinode_process_lib::{await_next_request_body, call_init, println, Address}; +use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response}; wit_bindgen::generate!({ - path: "wit", + path: "../../../wit", world: "process", exports: { world: Component, @@ -27,10 +27,7 @@ fn init(_our: Address) { return; }; - println!( - "{}", - String::from_utf8(args).unwrap_or("echo: error".into()) - ); + let _ = Response::new().body(args).send(); } ``` From writing applications, this should look very familiar - the imports, `wit_bindge::generate!`, `call_init!`, `init(our: Address)`, etc. are all exactly the same. @@ -40,6 +37,11 @@ Next, all we do is `String`ify the message body, and print it out. Arbitrary logic can be put below `await_next_message_body` - just like an app, you can fire-off a number of requests, choose to await their responses, handle errors, etc. just like normal. +In this case, we send a `Response` containing the arguments passed in. +The `Response` let's us compose `echo` with other scripts via [piping](../terminal.md#piping-and-composing-scripts). +Not every script needs to end with a `Response`. +It would be valid to simply `println` the `args` and terminate, but this would mean that our script has no "return value" to compose with other scripts via pipes. + ## Publishing a Script Unlike processes accociated with a long-running application, which will be put into the `manifest.json`, scripts must be registered in a separate `scripts.json` file. While very similar, there are a few important differences; let's take a look at an example that could live in your packages `pkg/scripts.json` file: diff --git a/src/terminal.md b/src/terminal.md index 897f2e8b..aa971cab 100644 --- a/src/terminal.md +++ b/src/terminal.md @@ -1,6 +1,6 @@ # Terminal -## Basic Usage and Utilities +## Commands All commands in the terminal are calling scripts - a special kind of process. KinodeOS comes pre-loaded with a number of scripts useful for debugging and everyday use. @@ -77,3 +77,130 @@ Example: ``` For more information on writing your own scripts, see the [cookbook](./cookbook/writing_scripts.md). + +## `scripts.json` +For your scripts to be usable by the terminal, you must include a `pkg/scripts.json` file. +The JSON object in `scripts.json` describes a configuration for a set of WebAssembly (WASM) modules. +Each top-level key represents the path of the WASM module in your package, usually just `"myscript.wasm"`, `"echo.wasm"`, etc. + +The value for each module is an object that specifies the configuration for that particular module. +The object can contain the following fields: + +- `root` (Boolean): Indicates whether the script has root privileges +- `public` (Boolean): Determines if the script is publicly accessible by other processes +- `requestNetworking` (Boolean): Specifies whether the script will get networking capabilities +- `requestCapabilities` (Array): An array that lists the capabilities requested by the script. Each element in the array can be either a string or an object. The string represents a `ProcessId` that this script will be able to message. When an object is used, it specifies a different kind of capability from `issuer` with `params` as an arbitrary json object. +- `grantCapabilities` (Array): An array `ProcessId`s as strings which represent which processes will be able to message this script back + +Modules may not necessarily use all these fields. For instance, "m.wasm" only uses root, public, and requestNetworking, omitting requestCapabilities and grantCapabilities. + +### Example +This is the actual `scripts.json` file for the default `terminal:sys` scripts: +```json +{ + "alias.wasm": { + "root": false, + "public": false, + "requestNetworking": false, + "requestCapabilities": [ + "terminal:terminal:sys" + ], + "grantCapabilities": [] + }, + "echo.wasm": { + "root": false, + "public": false, + "requestNetworking": false, + "requestCapabilities": [], + "grantCapabilities": [] + }, + "cat.wasm": { + "root": false, + "public": false, + "requestNetworking": false, + "requestCapabilities": [ + "vfs:distro:sys", + { + "process": "vfs:distro:sys", + "params": { + "root": true + } + } + ], + "grantCapabilities": [] + }, + "hi.wasm": { + "root": false, + "public": false, + "requestNetworking": true, + "requestCapabilities": [ + "net:distro:sys" + ], + "grantCapabilities": [ + "net:distro:sys" + ] + }, + "top.wasm": { + "root": false, + "public": false, + "requestNetworking": false, + "requestCapabilities": [ + "kernel:distro:sys" + ], + "grantCapabilities": [] + }, + "m.wasm": { + "root": true, + "public": false, + "requestNetworking": true + } +} +``` + +## Piping and Composing Scripts +### Using Pipes in the Terminal +The terminal lets you pipe the previous command's output into another script. +For exapmle: +```bash +echo default-router-1.os Hello! |1 hi +``` +Since the `echo` script just returns its arguments, we are piping the string `default-router-1.os Hello!` into `hi`, so the end result is just +```bash +hi default-router-1.os Hello! +``` +This is exactly the same as a unix pipe except for the number that comes after the pipe. +This number after the pipe represents timeout in seconds. +In this case: wait for `echo` to respond, and if it does not after 1 second, abort. +Any `u64` value is a valid timeout value. + +### Writing Your Script to be Pipe-able +Not all scripts are pipable. +For a script's output to be pipeable you must take the output and `.send()` it in a `Response`. +The [cookbook](./cookbook/writing_scripts.md) describes how every script begins with a `Request` that contains arguments in the `body`. +If you want the outputs of your script to be composable via pipes, you must terminate your script with a `Response` to this initial request. +As a simple example, let's look at the `echo` code: +```rust +use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response}; + +wit_bindgen::generate!({ + path: "../../../wit", + world: "process", + exports: { + world: Component, + }, +}); + +call_init!(init); + +fn init(_our: Address) { + let Ok(args) = await_next_request_body() else { + println!("echo: failed to get args, aborting"); + return; + }; + + let _ = Response::new().body(args).send(); +} +``` + +All we do here is get the args, and send a response back with the exact same args. +If instead of sending a `Response` we simply `println`d the arguments out, then we wouldn't be able to compose `echo` with pipes. From b61394ef1875cdae0c83b4c0768dc7ea6fcc78db Mon Sep 17 00:00:00 2001 From: Drew Tada Date: Tue, 30 Jan 2024 13:10:25 -0500 Subject: [PATCH 2/4] proofreading --- src/terminal.md | 51 +++---------------------------------------------- 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/src/terminal.md b/src/terminal.md index aa971cab..3f2529e4 100644 --- a/src/terminal.md +++ b/src/terminal.md @@ -78,9 +78,9 @@ Example: For more information on writing your own scripts, see the [cookbook](./cookbook/writing_scripts.md). -## `scripts.json` +## Packaging Scripts with `scripts.json` For your scripts to be usable by the terminal, you must include a `pkg/scripts.json` file. -The JSON object in `scripts.json` describes a configuration for a set of WebAssembly (WASM) modules. +The JSON object in `scripts.json` describes the configuration for each script in your package. Each top-level key represents the path of the WASM module in your package, usually just `"myscript.wasm"`, `"echo.wasm"`, etc. The value for each module is an object that specifies the configuration for that particular module. @@ -95,40 +95,9 @@ The object can contain the following fields: Modules may not necessarily use all these fields. For instance, "m.wasm" only uses root, public, and requestNetworking, omitting requestCapabilities and grantCapabilities. ### Example -This is the actual `scripts.json` file for the default `terminal:sys` scripts: +This is a `scripts.json` that publishes a single script, `hi`, which doesn't receive `root` capabilities, is not `public`, can send messages over the network, will receive the capability to message `net:distro:sys`, and gives `net:distro:sys` the ability to message it back: ```json { - "alias.wasm": { - "root": false, - "public": false, - "requestNetworking": false, - "requestCapabilities": [ - "terminal:terminal:sys" - ], - "grantCapabilities": [] - }, - "echo.wasm": { - "root": false, - "public": false, - "requestNetworking": false, - "requestCapabilities": [], - "grantCapabilities": [] - }, - "cat.wasm": { - "root": false, - "public": false, - "requestNetworking": false, - "requestCapabilities": [ - "vfs:distro:sys", - { - "process": "vfs:distro:sys", - "params": { - "root": true - } - } - ], - "grantCapabilities": [] - }, "hi.wasm": { "root": false, "public": false, @@ -139,20 +108,6 @@ This is the actual `scripts.json` file for the default `terminal:sys` scripts: "grantCapabilities": [ "net:distro:sys" ] - }, - "top.wasm": { - "root": false, - "public": false, - "requestNetworking": false, - "requestCapabilities": [ - "kernel:distro:sys" - ], - "grantCapabilities": [] - }, - "m.wasm": { - "root": true, - "public": false, - "requestNetworking": true } } ``` From e8b47f2c381edcf874f8494b037347dcef6a2bce Mon Sep 17 00:00:00 2001 From: Drew Tada Date: Tue, 30 Jan 2024 17:08:01 -0500 Subject: [PATCH 3/4] proofreading --- src/cookbook/writing_scripts.md | 2 +- src/terminal.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cookbook/writing_scripts.md b/src/cookbook/writing_scripts.md index 0c8de4d1..0e3fa273 100644 --- a/src/cookbook/writing_scripts.md +++ b/src/cookbook/writing_scripts.md @@ -12,7 +12,7 @@ Let's look at the simplest possible script: `echo`, which takes in an argument, use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response}; wit_bindgen::generate!({ - path: "../../../wit", + path: "wit", world: "process", exports: { world: Component, diff --git a/src/terminal.md b/src/terminal.md index 3f2529e4..1656a9fd 100644 --- a/src/terminal.md +++ b/src/terminal.md @@ -86,7 +86,7 @@ Each top-level key represents the path of the WASM module in your package, usual The value for each module is an object that specifies the configuration for that particular module. The object can contain the following fields: -- `root` (Boolean): Indicates whether the script has root privileges +- `root` (Boolean): Indicates whether the script has "root" privileges - meaning whether it gets *every* capability that the terminal has (not necessarily every capability in existence on your machine) - `public` (Boolean): Determines if the script is publicly accessible by other processes - `requestNetworking` (Boolean): Specifies whether the script will get networking capabilities - `requestCapabilities` (Array): An array that lists the capabilities requested by the script. Each element in the array can be either a string or an object. The string represents a `ProcessId` that this script will be able to message. When an object is used, it specifies a different kind of capability from `issuer` with `params` as an arbitrary json object. @@ -138,7 +138,7 @@ As a simple example, let's look at the `echo` code: use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response}; wit_bindgen::generate!({ - path: "../../../wit", + path: "wit", world: "process", exports: { world: Component, From 4b1db9264e3476f674fa49f46abf59a358f6606a Mon Sep 17 00:00:00 2001 From: Drew Tada Date: Tue, 30 Jan 2024 18:08:25 -0500 Subject: [PATCH 4/4] nick's suggestions --- src/apis/terminal.md | 5 ++++- src/cookbook/writing_scripts.md | 16 ++++++++-------- src/terminal.md | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/apis/terminal.md b/src/apis/terminal.md index 92dcf216..2a45f0fc 100644 --- a/src/apis/terminal.md +++ b/src/apis/terminal.md @@ -1,4 +1,7 @@ # Terminal API +It is extremely rare for an app to have direct access to the terminal api. +Normally, the terminal will be used to call scripts, which will have access to the process in question. +For documentation on using, writing, publishing, and composing scripts, see the [terminal use documentation](../terminal.md), or for a quick start, the [script cookbook](../cookbook/writing_scripts.md). The Kinode terminal is broken up into two segments: a Wasm app, called `terminal:terminal:sys`, and a runtime module called `terminal:distro:sys`. The Wasm app is the central area where terminal logic and authority live. @@ -21,7 +24,7 @@ For example, `hi:terminal:sys` can be shortened to just `hi` as in: `hi default- The other most commonly used script is `m:terminal:sys`, or just `m` - which stands for `Message`. `m` let's you send a request to any node or application like so: ```bash -m john.os@proc:pkg:pub {"foo":"bar"} +m john.os@proc:pkg:pub '{"foo":"bar"}' ``` Note that if your process has the ability to message the `terminal` app, then that process can call any script - of which there may be many on a machine, so we cannot possibly write all of them down in this document. diff --git a/src/cookbook/writing_scripts.md b/src/cookbook/writing_scripts.md index 0e3fa273..fd36a360 100644 --- a/src/cookbook/writing_scripts.md +++ b/src/cookbook/writing_scripts.md @@ -7,7 +7,7 @@ When writing a script, you cannot control the `OnExit` behavior like you can wit - Scripts are registered in the `scripts.json` file instead of the `manifest.json` file ## Writing a Script -Let's look at the simplest possible script: `echo`, which takes in an argument, and prints it out again: +Consider the simplest possible script: `echo`, which takes in an argument, and prints it out again: ```rust use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response}; @@ -30,21 +30,21 @@ fn init(_our: Address) { let _ = Response::new().body(args).send(); } ``` -From writing applications, this should look very familiar - the imports, `wit_bindge::generate!`, `call_init!`, `init(our: Address)`, etc. are all exactly the same. +From writing applications, this should look very familiar - the imports, `wit_bindgen::generate!`, `call_init!`, `init(our: Address)`, etc. are all exactly the same. The first unique thing about scripts is that we will have no `loop` where we `await_message`. Instead, our initial arguments will come from a single message from the terminal - which we get by calling `await_next_message_body()`. Next, all we do is `String`ify the message body, and print it out. Arbitrary logic can be put below `await_next_message_body` - just like an app, you can fire-off a number of requests, choose to await their responses, handle errors, etc. just like normal. -In this case, we send a `Response` containing the arguments passed in. -The `Response` let's us compose `echo` with other scripts via [piping](../terminal.md#piping-and-composing-scripts). +In this case, `echo` sends a `Response` containing the arguments passed in. +The `Response` enables composition of scripts via [piping](../terminal.md#piping-and-composing-scripts), so that the output of `echo` can serve as the input to the next program. Not every script needs to end with a `Response`. -It would be valid to simply `println` the `args` and terminate, but this would mean that our script has no "return value" to compose with other scripts via pipes. +It would be valid to simply `println` the `args` and terminate, but this would mean that the script has no "return value" to compose with other scripts via pipes. ## Publishing a Script -Unlike processes accociated with a long-running application, which will be put into the `manifest.json`, scripts must be registered in a separate `scripts.json` file. -While very similar, there are a few important differences; let's take a look at an example that could live in your packages `pkg/scripts.json` file: +Unlike processes associated with a long-running application, which will be put into the `manifest.json`, scripts must be registered in a separate `scripts.json` file. +While very similar, there are a few important differences; here's an example that could live in your packages `pkg/scripts.json` file: ```json { "echo.wasm": { @@ -60,7 +60,7 @@ This `scripts.json` file corresponds to a package which publishes a single scrip The keys of this object are the process paths inside of the `pkg/` folder. The name of the script will be the file path, with `.wasm` taken off. The object that `echo.wasm` points to is very similar to `manifest.json`, with a few things removed, and `root` has been added: -- `root` means that all the capabilities held by the `terminal:terminal:sys` are passed to this script. This is rarely needed. +- `root` means that all the capabilities held by the `terminal:terminal:sys` are passed to this script (this is powerful, and rarely needed) - `public`: same as `manfiest.json` - corresponds to whether or not other processes can message `echo.wasm` without the messsaging cap - `requestNetworking`: same as `manfiest.json` - corresponds to whether or not this script will need to send messaages over the network - `requestCapabilities`: same as `manifest.json` - a list of capabilities that will be granted to this script on startup (NOTE if you have `root`, there is no reason to populate `requestCapabilities` as well) diff --git a/src/terminal.md b/src/terminal.md index 1656a9fd..d6554784 100644 --- a/src/terminal.md +++ b/src/terminal.md @@ -3,7 +3,7 @@ ## Commands All commands in the terminal are calling scripts - a special kind of process. -KinodeOS comes pre-loaded with a number of scripts useful for debugging and everyday use. +Kinode OS comes pre-loaded with a number of scripts useful for debugging and everyday use. These scripts are fully named `