Installation

You can run Lince as an executable, here are the releases. Pick the latest one for your machine and operating system. After the download, unzip it and run the binary:

./lince

If you preffer to compile the source code, the easiest way is to download the repo and run:

cargo run

If you don't have cargo installed, you can install it with with Rustup.

Have fun.

Explanation

This is the documentation for the Lince project. If you find any errors feel free to submit a PR.

The tool used to construct this is the mdbook crate, which is used by the official Rust book and many other technologies for their documentation. We can customize the color palette, search, print, etc. mdbook uses Markdown (.md) files, supports hot-reloading during development, and outputs static files into a specified directory, which is ignored by Git for deployment purposes.

If you don't have Cargo installed yet, you can install it with Rustup. It comes with Rust and its toolchain—how convenient!

To install, build, and serve the manual easily, try running:

make book

If that doesn't work, try building and running the manual manually.

To install the tool that builds the manual, run:

cargo install mdbook

If cargo isn’t in your PATH, add it to your shell configuration (~/.bashrc or ~/.zshrc) so you don’t have to type the full path every time (i.e., ~/.cargo/bin/{program}). Run this in your terminal:

#bash
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc

With mdbook installed, enter the project folder and type:

mdbook serve

If you want to choose the port:

mdbook serve --port 9999

MdBook Documentation

Introduction

Lince is a tool for registry, interconnection and automation of Needs and Contributions with open scope.

The phrase above is the densest way to explain Lince. The current page serves as an overview, and the subsequent chapters cover it in much more detail. Let's start with the registry part:

Registry

Users will start their Lince application for the first time, and that will create a personal database

(for UNIX systems [Linux and macOS] it is located at '~/.config/lince/lince.db').

That database is a Sqlite file, that is reffered by the application as a DNA. That file can be altered to make the app behave differently.

The behavior can be of: setting personal/professional tasks, modeling personal items, scheduling computer actions, act out economic trades between parties, and many more, the sky, your imagination and computing power is the limit.

Any app behavior mentioned above can be boiled down to a Need, or a Contribution to a Need. The differentiator of the two is a Quantity. If such quantity is negative, that will be a Need.

Having -2 Apples means you need to go and get two apples to be neutral, to satiate your needs. The same applies to a habit, having a -1 Quantity in Exercise means you need to exercise.

The previous description is the basis of the whole app, everything revolves around it.

Automation

If there is predictability in your Needs, and a fixed frequency is enough to describe them, then a simple automation can be constructed.

One can set the automatic every day change of the 'Studying' habit's Quantity to -1. And with another feature, if that Quantity is -1 a Command is sent to the computer that closes all programs that are gaming related.

Another point of automation is the forementioned DNA. One aspect of constructing systems is that many great ideas often are lost with time, memory and the passing away of those contributors. Common patterns and knowledge about production and maintenance of commodities, economic trades, access to resources and ways of living is scattered.

These patterns could be saved into some form of database, that when fed into a program like Lince, will create Needs and Contributions, so users can have a headstart in any endeavor. From production according to a bill-of-materials to morning routines and parties organization, these activities create Needs for gathering resources and acting out tasks.

Contributions to Lince DNAs are like creating blueprints for others to get up and running. They might not be a perfect fit, but allow for great customization. Currently the way of centralizing DNAs is the Github Repository.

Interconnection

With great customizability and automation of people's DNAs, the next barrier is the connection of those individual apps, turning each machine into a p2p node, also called a Lince Cell. The same way you create triggers and rules for the quantites of your DNA, you can do the same for others.

If one Market has 200 apples and you need 5 you can create a Transfer Proposal. The Quantity of money diminishes on your side, and the Apple one increases. On the Market side, a manual price can be inputed and accepted when proposals come, or it can be adaptive. Donations of clothes and asking people close to you for a tool would be similar, just without the money part.

Organization

Disclaimers

All the repositories in the Lince ecosystem have the GNU GPLv3 license. Lince is a non-profit project and crowdfunding is the source of development compensation:

GitHub Sponsors | Patreon | Apoia.se

Lince tries to facilitate and automate the connection between people and resources, by transforming needs and contributions into data. The gains and losses related to the interaction, such as transportation, production and services themselves, remain the responsibility and risk of the parties involved.

Contributions

The workflow of contributing to the Lince ecosystem can be of simply making a Pull Request. But if one wants to have certainty of developing a wanted feature, the best way is to see the DNA of Lince programming tasks.

The online resource to communicate is the Discord.

The git repositories hosted currently in the lince-social GitHub Organization mean this:

  • lince: the app's code.
  • dna: the databases for different Lince behaviors.
  • lince-social.github.io: this documentation.
  • cub: homework and challenges to understand how do use Lince, from begginer to advanced.
  • mosca: tool for different computer actions, like cleaning the system, linking dotfiles and more. The goal is that the Command table calls this binary with different flags for common tasks. This repo is what should evolve into an Operating System or customization of an existing one.
  • .github: general stuff, like photos, icons and information about GitHub sponsors.

Tech Stack

Rust

Rust is a Procedural, Compiled, Borrow Checked (Memory Safe(r)), Statically Typed, Lower Level Than Go And Higher Level Than C programming language. It offers great concurrency, async (hard) and robust unwanted behavior elimination. Has a powerful standard library but shines greatly with crates (dependencies), using cargo, it's package management system.

There are many ways to write procedures like any language. Some more idiomatic than others, with one liners, syntax sugar and macros. This manual serves as a way of helping to write good Rust procedures, outside of executing Clippy's suggestions (more info below).

You install Rust preferably through Rustup, which comes with the whole toolchain (you are going to need it), including:

  • rustc: the Rust compiler.
  • cargo: the package manager and build tool.
  • rustfmt: formats Rust code according to style guidelines.
  • clippy: a linter that provides suggestions to improve your code (micro best practices).
  • rust-analyzer: a language server for IDE support and code navigation.

Cool things that are built with Rust:

Read the next session to create a project and force me to put it in the list.

Rust

Learning Resources:

  1. Rust Book: chapters 1-6 will give you a simple base if you just want to hack something together. Passing through the entire book, doing the examples and understanding them deeply, will set you up for great vibe coding.

  2. Rustlings: for when you are done with the book. You will complete exercises on a broad spectrum of the language's features.

  3. Rust for Rustaceans: for a deeper dive in the language, good practices and crate recomendations.

  4. This Week in Rust: a weekly newsletter about Rust projects, crates and language changes. Amazing for keeping up to date with the ecosystem.

  5. Youtube Channels: Jon Gjengset | No Boilerplate | The Rustagen | Tsoding |Code to the Moon | fasterthanlime | chris biscardi | Mike Code

  6. Podcast: Rustacean Station

  7. Discord, Matrix and many more online communities are very welcoming to newbies.

Debugging

1. Ergonomics

To observe values during debugging, you can use:

#![allow(unused)]
fn main() {
println!("User: {}", user);
}

However, if a message alongside the value isn't necessary, it's easier to write:

#![allow(unused)]
fn main() {
dbg!(&user);
}

Example:

#[derive(Debug)]
struct User {
    name: String,
}

fn main() {
    let user = User {
        name: "Chose".to_string(),
    };

    dbg!(&user);
    println!("{user:?}");
    println!("User's name is: {}", user.name)
}

You can run the snippet, and it will show only the 'println!' lines, not the 'dbg!'. The real stdout output would be this:

 [src/main.rs:11:5] &user = User {
     name: "Chose",
 }
 User { name: "Chose" }
 User's name is: Chose

We can see that the dbg! macro outputs more information with less effort. Here's a great example: link.

There are some crates to help your CI:

Cargo Audit

Audit your dependencies for crates with security vulnerabilities reported to the RustSec Advisory Database.

# Install
cargo install cargo-audit --locked

# Run
cargo audit

Cargo Udeps

See your unused cargo dependencies

# Install
cargo install cargo-udeps --locked

# Run
cargo +nightly udeps

Cargo Vet

[...] tool to help projects ensure that third-party Rust dependencies have been audited by a trusted entity.

# Install
cargo install cargo-vet --locked

# Initialize a standard Vet criteria, this can be changed
cargo vet init

# Run
cargo vet

Tables

This is where the real fun begins.

Record

record
id
quantity
head
body
location

Lince is centered on the 'record' table, but like, according to the creator... like... what does he know?


recordDATA STRUCTURE
idINT
quantityFLOAT
headSTRING
bodySTRING
location3D POINT (still thinking)

'id's are automatically generated.

'quantity' represents the availability of the record, if negative it is a Necessity, if positive, a Contribution, zero makes it not mean much, sometimes.

'head' and 'body' are meant to be parts of a whole, where one can be used for a short summary and the other a description, or one has all the information and the other holds tags for filtering through views. With a pubsub protocol, one can send a short information of the record, in this case it can be the head, and put the rest in the body. Only those interested in the head will ask for the body of the record. That way the minimum amount of information is sent over the network, making it faster and stuff, I think.

'location' is an important information for interactions outside of computers (they exist, it's insane) or any other use you want to give it.


recordDATA STRUCTUREUSER INPUT
idINT
quantityFLOAT-1
headSTRINGEat Apple
bodySTRING
locationPOINT

So, for an example, imagine that you like apples and you want to create a task to eat it today.

You create a 'record', giving it '-1' to the 'quantity', for that action is a Necessity in your life right now, and 'Eat Apple' to the 'head'.


recordDATA STRUCTUREUSER INPUTACTUAL RECORD
idINT1
quantityFLOAT-1-1
headSTRINGEat AppleEat Apple
bodySTRINGNULL
locationPOINTNULL

The end result, on the database, is this record.


Here is an example of different possible records for individual items and actions.

idquantityheadbodylocation
1-1Eat AppleNULLNULL
2-1AppleItem, FoodNULL
30Brush TeethAction, HygieneNULL
43ToothbrushItem, HygieneNULL
5-1MeditateActionNULL

Views

COLUMNSDATA STRUCTURE
idINT
nameSTRING
querySTRING

Views are ways to select records.

A view will have a name, so it is human readable and a query to display different data.

The query is made with SQL allowing you to select what columns you want to see, filtered, ordered and much more, just the way you want it.

Examples:

Let's grab the record table shown before.

idquantityheadbodylocation
1-1Eat AppleNULLNULL
2-1AppleItem, FoodNULL
30Brush TeethAction, HygieneNULL
43ToothbrushItem, HygieneNULL
5-1MeditateActionNULL

A view of things you need to buy may look like this:

COLUMNVALUE
id1
nameBuy
querySELECT * FROM record WHERE LOWER(body) LIKE '%item%' AND quantity < 0

And in this case, will display only:

idquantityheadbodylocation
2-1AppleItem, FoodNULL

Since no other records with a body containing 'Item' (lower, upper, pascal case...) exist with a negative quantity.

Collection

|

Collection View

Configuration

Table: configuration

configurationDATA STRUCTURE
idSERIAL
quantityREAL
languageVARCHAR
timezoneVARCHAR

This table is responsible for changing the behavior of Lince. The 'quantity' sets the active configuration, with the value of 1, the other rows have it 0. 'save_mode' can be automatic or manual, happening only when the user demands it, or after every database change. 'view_id' sets the rows you will see, this is a reference to the id of a row in view. 'column_information_mode' can be 'silent for no information, 'short', for the data types and constraints or 'verbose' for the previous information plus a description of the functionality/usage of the column. Keymap is for changing the commands sent manually (TODO) so 'd' is not for deleting records but for 'd'escribing lince, printing it's documentation. 'truncation' is somewhat deprecated, used for truncating cells's contents. 'table_query' is too deprecated, somewhat, from the terminal days, but essentially it changes how a table is queried. 'language' and 'timezone' are for the language and timezone :).

Karma

karmaDATA STRUCTURE
idSERIAL
quantityINTEGER
expressionTEXT

Karma has it's own section further below for a deeper understanding, it's a way of combining data and taking actions automatically. The 'expression' column contains lince-python-like logic.

KARMA

The 'expression' column in the 'karma' table is like Lince's brain.

It is divided into two parts left and right, like a brain, separated by a '='. For karma to have it's effect, a karma() function is called (currently every 60 seconds), in each expression first the right side has some information searched and replaced if necessary, then it is evaluated, and passed to the left side. (What?)

Let's look at an example, say you have a record:

recordVALUE
id1
quantity0
headEat Apple
bodyAction, Food
location

If we pull the Record Quantity (rq) of this record we get zero (0). In other words, if we pull the rq1, the Record Quantity of the record with ID 1, we get zero (0).

Our karma expression can be a simple: | karma | VALUE | |------------|----------| | id | 1 | | quantity | 1 | | expression | rq1 = -1 |

For demonstrating purposes, karma expressions will be shown like this, no need for the full table:

rq1 = -1

When the karma() function is called, every row will have it's consequence if needed. In this case the consequence is that the quantity in the record with ID 1 is now -1.

How does that happen? The right side of the expression, in this case '-1' is evaluated, a python eval(). The right side is now literal python, you can declare classes, pull libraries, write everything, including writing just -1. The left side of the expression, since it has only the mention of a record quantity will receive that number without problems, the quantity in the record with ID 1 is now -1.


Simple Example:

Now let's say that the rq1 is still zero (0), another karma expression could be:

rq1 = rq1 -1

So now everytime karma() is called, it will diminish the quantity by one, insead of always setting it to -1. The rq1 is searched and replaced with it's actual value:

rq1 = rq1 -1

      |
      V

rq1 = 0 -1

       |
       V

rq1 = -1

Now your rq1 is -1:

recordVALUE
id1
quantity-1
headEat Apple
bodyAction, Food
location

The Zero Case:

And what if the rq1 was 1? In that case, it would still be one, why? Because when the right side is zero, nothing changes on the left side. That is not a bug, but a feature, that way, expressions can be ignored if they dont meet certain conditions:

rq1 = -1 * (rq1 == 10)

In this case the '(rq1 == 10)' in python will be set to False, because '(1 == 10)' is False, and when '-1 * False' is read by python it returns zero. So the record quantity will remain 1.

But what if I want it to be 0? Then you add '*' before the '=':

rq1 *= -1 * (rq1 == 10)

If rq1 is ten (10), it turns into one (1), if it is not ten (10), then it turns into zero (0).


How it works, really:

A karma expression is a mix of python and regex for searching and replacing, but that is not all it can do. One of the features is to be able to run commands located in the command table.

Let's say you have a command:

| command | VALUE | | -------- | --------------- | ------------------------------------------- | | id | 3 | | quantity | 1 | | command | touch grass.txt | <-- touch in Linux creates the file. |

This command with ID 3 can be referenced in a karma expression with 'c3'.

If we have a record:

commanVALUE
id2
quantity1
headCreate grass.txt
bodyAction, Command
location

We can create a semi-automation, reducing the steps, the clicks, the button presses, the time it takes to do that job:

rq2,c3 = (rq2 == 0) * 1

When the quantity in record with ID 2 is 0, the command with ID 3 is run through the computer's shell and the grass.txt is created.


Automation, real automation:

But what if we want to automate everything? Like literally everything.

For that, a general enough system needs to exist. When we talk automation we talk about labor automation, time that does not need to be spent doing something. Automation is a tool, not an end, and it can be a tool for the benefit of everyone, by reducing the labor needed to satiate human needs.

Lince aims to be a tool that can model human needs and contributions to meet those needs, automating as much as possible for that goal. And the biggest source of automation in Lince, is not shortening the time it takes to give the computer an instruction, but removing the need to do it, satiating that need.

The 'frequency' table allows for rows with different frequencies to return 1 to a karma expression, activating scripts changing the values in certain cells in certain tables, let's see it in action:

frequencyVALUE
id6
quantity1
day_week
months
days1
seconds
next_date2024-01-01 22:00:00+00:00
finish_date
when_done

Let's say that we are in 2024-01-01 21:00:00 living in a place with a +0 timezone and have a karma expression:

c3 = f6

     |
     V

c3 = 0

In that same day, when ten (10) P.M. rolls around, the time is exactly '2024-01-02 22:00:00+00:00' or after, the karma() function will receive not zero (0), but one (1) from the frequency with ID 6:

c3 = 1

The command will receive one, it will be run, the 'touch grass.tx' will be executed, the grass.txt file will be created. It achieves that by updating the date in next_date by one day, as set by the day column with value one (1) in this case.

In the TODO list, ignore if you want: make all the expressions that use a certain frequency be checked and run if needed before updating next_date. Right now one activation of a frequency negates all other expressions that need to return 1 at that frequency, because it will already have been activated and updated.


Until now the command was only at the left side, as something that was run because of a consequence on the right side. But, it can also be run and it's output passed as a value from the right side to the left one:

commandVALUE
id7
quantity1
commandpython temperature_getter.py
recordVALUE
id10
quantity0
headGet a sweater
bodyAction, Clothes
location

This pseudo python script uses a weather API to get the tomorrow's lowest temperature (in Celsius, obviously) in a certain city, it then prints it to the terminal, very UNIX-like. If we have that, we can ask Lince to put that temperature inside a karma expression and use it.

rq10 = -f6 * (co7 <= 15)

Notice it says 'co7', there is a letter 'o' because it's supposed to be run a command that outputs the result to a file, so lince can read it. After that, by reusing a previously created frequency, we can create an expression that makes the quantity of the record with ID 10 be -1 at 22h of everyday, if the command with ID 7 returns tommorow's lowest temperature as a value equal to or less then 15. If we have a view that shows only records with a negative quantity, a person will only see the necessity of packing a sweater when tomorrow is 'cold' enough.


When opening the browser version you will see a view and this table:

AppOperationsTables
[E] Exit[C] Create[0] Configuration
[H] Help[R] Read[1] History
[S] Save DB[U] Update[2] Record
[L] Load DB[D] Delete[3] Karma
[A] Activate Config[Q] Query[4] Frequency
[F] SQL File[5] Command
[6] Sum
[7] Transfer
[8] View

As you can see, this menu has letters and numbers. All the numbers in the 'Tables' column can be combined with the CRUD operations in the 'Operations' column. so if you want to create, delete, update, or read a row you can input in the input box at the top of the screen the number of the table, and the operation, so deleting rows from the 'karma' table would be '3d'. If you type one of the crud operations without anumber, it defaults to the record table. Because Lince is still in development, the place for you to write what you want to delete is shown in the window you opened Lince, in the terminal that is. There you will see a python input() for the creation, deletion and updating of table rows. The read operation is also a work in progress, as it used to work in the terminal and is a sort of a 'Jerry-rig' or 'gambiarra'.

Just typing a number will make the record with ID of that number to have it's quantity set to zero.

Since the config has a view_id, you will create a view, and put it in as many configs as you want to. To change configs, and also the view, you write 'a' followed by a number, corresponding to the configuration's ID, so 'a2' will change to configuration 2; that configuration might have a 'view_id' of 30, so 'a2' will give me also the view of the records queried by the view with ID 30.

That's also something of the terminal days. There are plans of making buttons and stuff, but only because people think they need them, with the box up top for writting commands everything happens fast and you can keep you hands on the keyboard, the best way of using computers.

Karma Condition

Karma Consequence

Frequency

frequencyDATA STRUCTURE
idSERIAL
quantityINTEGER
day_weekREAL
monthsREAL
daysREAL
secondsREAL
next_dateTIMESTAMP
finish_dateDATE
catch_up_sumINTEGER

'frequency' contains a plethora of ways for modeling a frequency, this is a table that makes more sense when one understands karma. If one wants to automate something that happens, changes every 4 days 9 hours and 53 seconds, finishing at 2029, it can be done so. 'day_week' is the day of the week, monday is 1. 'months', 'days' and 'seconds' are what they seem, 'next_date' is the column that changes when the time comes, advancing to a next date in accordance with the previous columns. 'finish_date' asserts that it will only run before a certain date, 'catch_up_sum' is a value that when negative and the 'next_date' is way behind the current date, the returned value will be of a certain amount, so instead of returning one, it will return the absolute value of this column. Example:

There is one frequency for every 60 seconds, starting now, but I have activated it with karma 10 minutes from now. With a 'catch_up_sum' of -2 it will return 2, it should return 10, for it's been 10 minutes stopped, and then return 1 every 1 minute, but with a 'catch_up_sum' of -2 it returns 2. If the 'catch_up_sum' is more than zero, it will multiply the value returned, so if instead of -2 it's '2.5, after 10 minutes from being stopped it will return 25, and 2.5 every subsequent minute.

Command

commandDATA STRUCTURE
idSERIAL
quantityINTEGER
commandTEXT

'command' is a command you can run in through a shell, like 'python app.py' or 'touch grass.txt'.

Query

Sum

sumDATA STRUCTURE
idSERIAL
quantityINTEGER
record_idINTEGER
interval_relativeBOOL
interval_lengthINTERVAL
sum_modeINTEGER
end_lagINTERVAL
end_dateTIMESTAMP

This table is responsible for returning a sum of change. Change of a record's quantity over time. You have the 'record_id' to know what record to look for. 'interval_length' is a period, can be 1 day, 8 months, etc. If 'interval_relative' is TRUE, will sum all the changes ending now, starting at the past based on the 'interval_length' (ex: 'interval_length' of 8 months and 'interval_relative' is TRUE will make it look at the past 8 months). If FALSE an 'end_date' will need to be supplied. so the past 8 months ending in a certain date. 'end_lag' will give a space between now and the end point of a relative interval. So if the 'interval_length' is 8 months and 'interval_relative' is TRUE, an 'end_lag' of 2 months will make the sum look at the 2 months back to 10 months back, starting 10 months ago, ending 2 months ago. 'sum_mode' tells Lince what to sum, if 0 it's a delta, essentially doing a quantity now - quantity earlier. If it's negative, it sums all negative changes, positive, idem.

Examples

Transfer

Table: transfer

transferDATA STRUCTURE
idSERIAL
records_receivedJSON
records_contributedJSON
agreementJSON
agreement_timeTIMESTAMP
transfer_confirmationJSON
transfer_timeTIMESTAMP

WORK IN PROGRESS 'records_received' is a collection of records and their quantities that will interact with our records, things you will receive. 'records_contributed' are the records you will contribute and their quantities, to the records of other parties, can be more than one party. So you can receive 5 moneys and an apple for driving someone from A to B. You don't work with it, but you have a car and their destination was on the way of yours. 'agreement' is a collection of agreement by all parties involved. 'agreement_time' is the moment every party agreed for the conditions of the trade, who will receive what. 'transfer_confirmation' is also a collection but with a confirmation from all parties that the transfer was successful, and 'transfer_time' for saving the event's moment.

Dna

History

historyDATA STRUCTURE
idSERIAL
record_idINTEGER
change_timeTIMESTAMP
old_quantityREAL
new_quantityREAL

History is a table that automatically logs the change of a record's quantity, to use the 'sum' table.

Good Practices

Karma

Until Lince has Deterministic Simulation Testing (DST), you have to be mindfull of the Command table you produce, every command you set may possibly break your system if you don't tidy things up. If you have logic of running a command every hour if one record has quantity > X and you forget about it, any simple change will trigger it, so put guardrails for running things, be it Commands, Queries or even changing Record Quantities. Changes might cascade and deliver unforeseen consequences.

With DST this is easier to do, the plan is to have a containerized environment that runs a simulation of your system, isolated from the outside world. Your DNA (your personal configuration of lince) is a seed that can be run multiple times arriving at the same result (hopefully). Being able to run it with a high speed, changing the date (affecting the Frequency table) and record quantities will bring reproducible results. When you want to add something to your DNA you can check it's effects with a simulation and get info if it breaks anything in an edge case.

Command

Lince works with Sqlite files for it's database. It is recomended to frequently backup your DNAs, weekly, daily or hourly if you are paranoid. If some error or mistake happens, your information is safe.

Author's Style

Personal Note & Tips:

Modeling all items and actions to perform takes a while. Making a Lince instance, understanding the possibilities is to me a long term effort, ever increasing the value you get from computers. The possibilites are vast, limited by one's domain of computers and imagination, when combined, wizardry skills arise.

From a usage standpoint, I recommend having several views, one for each table and a personal view for tasks, mine is:

SELECT id, head FROM record WHERE quantity < 0 or LOWER(body) IS NULL

I also use the 'body' column in 'record' table as a tag holder, so every area in my life has either 'Action' or 'Item' and 'AREA'. With that, I have a view for every area and change configs rapidly with a2, a4, a3 so I can view the commands for running things or setting things as done (I don't remmember them all).

Tables

Karma

Tasks

Items