cyb/docs/scripting.md

soul: scripting guide

cyb uses rune Language for embeddable scripting aka cybscript.

rune is virtual machine that runs inside cyb and process all commands and rendering results

why we choose new language?

rune is dynamic, compact, portable, async and fast scripting language which is specially targeted to rust developers.

to our dismay we was not able to find any other way to provide dynamic execution in existing browsers which are compatable with future wasm based browsers.

rune is wasm module written in rust.

we hope you will enjoy it.

Using cybscript any cyber citizen can

  • tune-up his soul
  • extend and modify robot behaivior and functionality

soul is stored in ipfs and is linked directly to avatars passport.

CYB module

cyb module provide bindings that connect cyber-scripting with app and extend Rune language functionality.

Distributed computing

Allows to evaluate code from external IPFS scripts in verifiable way, execute remote computations

// Evaluate function from IPFS
cyb::eval_script_from_ipfs(cid,'func_name', #{'name': 'john-the-baptist', 'evangelist': true, 'age': 33})

// Evaluate remote function(in developement)
cyb::eval_remote_script(peer_id, func_name, params)

Passport

Get info about Citizenship

// Get passport data by nickname
cyb::get_passport_by_nickname(nickname: string) -> json;

Cyber links

Work with Knowelege Graph

cyb::get_cyberlinks_from_cid(cid: string) -> json;
cyb::get_cyberlinks_to_cid(cid: string)  -> json;

// Search links by text or cid
cyb::cyber_search(query: string) -> json;

// Create cyber link
// (this action requires trigger signer)
cyb::cyber_link(from_cid: string, to_cid: string);

IPFS

Work with IPFS

cyb::get_text_from_ipfs(cid: string) -> string;
cyb::add_content_to_ipfs(text: string);

Local relational-graph-vector database

Access to cozo and your brain data represented in text and vector-based formats.

// return N closest particles based on embeddings of each
cyb::search_by_embedding(text:string, count: int) -> string[];

Experemental

OpenAI promts(beta)

  • api key should be added using cyb-keys
  • this is wrapper around openai api
// Apply prompt OpenAI and get result
cyb::open_ai_completions(messages: object[]; apiKey: string;  params: json) -> string | AsyncIterable<string>;

Debug

Logging and debug methods

// Add debug info to script output
dbg(`personal_processor ${cid} ${content_type} ${content}`);

// console.log
cyb:log("blah");

Entrypoints

Entrypoint is important concept of cyber scripting, literally that is the place where cyber-script is inlined into app pipeline. At the moment all entrypoint types is related to some particle in cyber:

  • Moon Domain Resolver
  • Personal Processor
  • Ask Companion

Entrypoint principles

Each entrypoint is function that accept some arguments as input.

pub async fn personal_processor(cid, content_type, content) {
    // ... //
}

personal_processor is entrypoint for each-single particle(see. furter)

Second convention that each entrypoint should return output object - with one required property named action and some optional props depending on action for ex. #{action: 'pass'}.

Cyber-scripting has helpers to construct object-like responses, with mandatory "action" field and some optional fields:

pass() // pass untouched = #{action: 'pass'}

hide() // hide particle = #{ "action": "hide" }

cid_result(cid) // change particle's cid and parse = #{ "action": "cid_result", "cid": cid  }

content_result(content) //  modify particle content = #{ "action": "content_result", "content": content }

error(message) // error ^_^ = #{ "action": "error", "message": message }

So minimal entrypoint looks like this:

pub async fn personal_processor(cid, content_type, content) {
    return pass() // keep data stream untouched
}

Entrypoint types

Particle processor

Every single particle goes thru the pipeline and personal_processor function is applied to it content:

graph LR
A[particle] -- cid --> B[IPFS] -- content --> C(("personal<br />processor")) -- content mutation --> D(app)
// params:
//      cid: CID of the content
//      content_type: text, image, link, pdf, video, directory, html etc...
//      content: (text only supported at the moment)
pub async fn personal_processor(cid, content_type, content) {
    /* SOME CODE */
}

User can do any transformation of the particle in pipeline

// Update content
return content_result("Transformed content")

// Replace CID -> re-apply new particle
return cid_result("Qm.....")

// Hide item from UI
return hide()

// Keep it as is
return pass()

.moon domain resolver

Every user can write his own .moon domain resolver: [username].moon. When any other user meep particle with exactly such text, entrypoint will be executed.

graph LR
B(username.moon) -- username --> C["cyber<br/>passport<br/>particle"] --  cid --> D(IPFS) -- script code --> E(( particle<br/> resolver)) -- render content --> F(result)

Minimal resolver can looks like this: * no input params but context is used(user that look at your domain)

pub async fn moon_domain_resolver() {

    // particle consumer from context
    let name = cyb::context.nickname;

    // respond
    // as string
    return content_result(`Hello ${name}!`)

    // or CID(can be any text/image or even app hosted inside IPFS)
    // return cid_result("QmcqikiVZJLmum6QRDH7kmLSUuvoPvNiDnCKY4A5nuRw17")
}

And there is minimal personal processor that process domain and run resolver from remote user script.

pub async fn personal_processor(cid, content_type, content) {
    // check if text is passed here and it's looks like .moon domain
    if content_type == "text" and content.ends_with(".moon") {
            let items = content.split(".").collect::<Vec>();
            let username = items[0];
            let ext = items[1];
            if username.len() <= 14 && ext == "moon" {

                // get citizenship data by username
                let passport = cyb::get_passport_by_nickname(username).await;

                // get personal particle
                let particle_cid = passport["extension"]["particle"];

                // log to browser console
                cyb::log(`Resolve ${username} domain from passport particle '${particle_cid}'`);

                // execute user 'moon_domain_resolver' function from 'soul' script with zero params
                return cyb::eval_script_from_ipfs(particle_cid, "moon_domain_resolver", []).await;
        }
    }
}

Ask Companion

User can extend UI of the particle with custom controls. User can pass meta items as script result and cyb will render as UI extension. At the moment we have 2 meta UI items:

  • text: meta_text("title")
  • link: meta_link("/@master", "link to user named @master")
graph LR
E(user) -- search input --> C(("ask<br/>Companion")) -- companion payload --> D(meta UI)
pub async fn ask_companion(cid, content_type, content) {
    // plain text item
    let links = [meta_text("similar:")];

    // search closest 5 particles using local data from the brain(embedding-search)
    let similar_results = cyb::search_by_embedding(content, 5).await;


    for v in similar_results {
        // link item
        links.push(meta_link(`/oracle/ask/${v.cid}`, v.text));
    }

    return content_result(links)
}

Context

One of important thing, that can be used inside scripting is the context. Context point to place and obstacles where entrypoint was triggered. Context is stored in cyb::context and contains such values:

  • params(url params)
    • path / query / search
  • user(user that executes entrypoint)
    • address / nickname / passport
  • secrets(key-value list from the cyber app)
    • key/value storage

// nick of user that see this particle(in case of domain resolver)
let name = cyb::context.user.nickname;

// user particle that contains soul, that can be interracted directly from your soul
let particle = cyb::context.particle;

// Get list of url parameters (in progress)
let path = cyb::context.params.path;

//get some secret (in progress)
let open_ai_api_key = cyb::context.secrets.open_ai_api_key;

Advanced examples

Personal processor

// your content for <citizen_name>.moon domain
pub async fn moon_domain_resolver() {
    // get nickname of domain resolver at the momemnt
    let nickname =  cyb::context.user.nickname;

    let rng = rand::WyRand::new();
    let rand_int = rng.int_range(0, 999999);

    return content_result(`Hello, ${nickname}, your lucky number is ${rand_int} 🎉`);

    // substitute with some CID (ipfs hosted app in this case)
    // return cid_result("QmcqikiVZJLmum6QRDH7kmLSUuvoPvNiDnCKY4A5nuRw17")
}

// Extend particle page with custom UI elements
pub async fn ask_companion(cid, content_type, content) {
    // plain text item
    let links = [meta_text("similar:")];

    // search closest 5 particles using local data from the brain
    let similar_results = cyb::search_by_embedding(content, 5).await;


    for v in similar_results {
        // link item
        links.push(meta_link(`/oracle/ask/${v.cid}`, v.text));
    }

    return content_result(links)
}

// Transform content of the particle
pub async fn personal_processor(cid, content_type, content) {

    // skip any non-text content
    if content_type != "text" {
        return pass()
    }

    // <citizen_name>.moon domain resolver
    if content.ends_with(".moon") {
        let items = content.split(".").collect::<Vec>();

        let username = items[0];
        let ext = items[1];

        if username.len() <= 14 && ext == "moon" {

            // get passport data by username
            let passport = cyb::get_passport_by_nickname(username).await;

            // particle - CID of soul script
            let particle_cid = passport["extension"]["particle"];

            cyb::log(`Resolve ${username} domain from passport particle '${particle_cid}'`);

            // resolve content(script) by cid
            // evaluate 'moon_domain_resolver' from that
            let result = cyb::eval_script_from_ipfs(particle_cid, "moon_domain_resolver", []).await;

            return result
        }
    }

    // example of content exclusion from the search results
    let buzz_word = "пиздопроебанное хуеплетство";

    if content.contains(buzz_word) {
        cyb::log(`Hide ${cid} item because of '${buzz_word}' in the content`);
        return hide()
    }


    // example of content modification
    // replaces cyber with cyber❤
    let highlight_text = "cyber";
    let highlight_with = "❤";

    if content.contains(highlight_text) {
        cyb::log(`Update ${cid} content, highlight ${highlight_text}${highlight_with}`);
        return content_result(content.replace(highlight_text, `${highlight_text}${highlight_with}`))
    }

    // replace <token_name>@NOW (ex. bitcoin@NOW) with actual price in usdt
    // using external api call
    if content.contains("@NOW") {
        let left_part = content.split("@NOW").next().unwrap();
        let token_name = left_part.split(" ").rev().next().unwrap();
        let vs_currency = "usd";

        // external url call
        let json =  http::get(`https://api.coingecko.com/api/v3/simple/price?ids=${token_name}&vs_currencies=${vs_currency}`).await?.json().await?;
        return content_result(content.replace(`${token_name}@NOW`, `Current ${token_name} price is ${json[token_name][vs_currency]} ${vs_currency}`))
    }

    // anything else - pass as is
    pass()
}

Synonyms

cyb/src/contexts/scripting
scripting
cyb/src/services/scripting
scripting

Neighbours