about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--modules/by-name/ta/taskwarrior/module.nix2
-rw-r--r--modules/home.legacy/conf/xdg/default.nix22
-rwxr-xr-xmodules/home.legacy/conf/xdg/url-handler.sh (renamed from modules/home.legacy/conf/xdg/url_handler.sh)0
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.lock235
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.toml4
-rw-r--r--pkgs/by-name/ts/tskm/build.rs3
-rw-r--r--pkgs/by-name/ts/tskm/flake.nix4
-rw-r--r--pkgs/by-name/ts/tskm/package.nix2
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs28
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs41
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/handle.rs27
-rw-r--r--pkgs/by-name/ts/tskm/src/main.rs15
-rw-r--r--pkgs/by-name/ts/tskm/src/state.rs45
-rw-r--r--pkgs/by-name/ts/tskm/src/task/mod.rs176
14 files changed, 475 insertions, 129 deletions
diff --git a/modules/by-name/ta/taskwarrior/module.nix b/modules/by-name/ta/taskwarrior/module.nix
index 354d3724..c5549ac9 100644
--- a/modules/by-name/ta/taskwarrior/module.nix
+++ b/modules/by-name/ta/taskwarrior/module.nix
@@ -35,7 +35,7 @@
 
           enable_hook_dbg() {
               # TODO: We should probably be smarter with the debug detection <2025-04-04>
-              if echo "$2" | grep --quite 'rc.debug.hooks='; then
+              if echo "$2" | grep --quiet 'rc.debug.hooks='; then
                 set -x
                 mkdir --parents "$HOME/.cache/task"
                 exec >>"$HOME/.cache/task/hook.log.$1"
diff --git a/modules/home.legacy/conf/xdg/default.nix b/modules/home.legacy/conf/xdg/default.nix
index 9751d806..ad0cd226 100644
--- a/modules/home.legacy/conf/xdg/default.nix
+++ b/modules/home.legacy/conf/xdg/default.nix
@@ -3,9 +3,9 @@
   lib,
   ...
 }: let
-  url_handler = pkgs.writeShellApplication {
+  url-handler = pkgs.writeShellApplication {
     name = "url-handler";
-    text = builtins.readFile ./url_handler.sh;
+    text = builtins.readFile ./url-handler.sh;
     inheritPath = false;
     runtimeInputs = [
       pkgs.rofi
@@ -23,22 +23,22 @@ in {
     mimeApps = {
       enable = true;
       defaultApplications = {
-        "application/pdf" = ["url_handler.desktop"];
-        "application/x-pdf" = ["url_handler.desktop"];
+        "application/pdf" = ["url-handler.desktop"];
+        "application/x-pdf" = ["url-handler.desktop"];
 
-        "text/html" = ["url_handler.desktop"];
-        "text/xml" = ["url_handler.desktop"];
-        "x-scheme-handler/http" = ["url_handler.desktop"];
-        "x-scheme-handler/https" = ["url_handler.desktop"];
-        "x-scheme-handler/about" = ["url_handler.desktop"];
-        "x-scheme-handler/unknown" = ["url_handler.desktop"];
+        "text/html" = ["url-handler.desktop"];
+        "text/xml" = ["url-handler.desktop"];
+        "x-scheme-handler/http" = ["url-handler.desktop"];
+        "x-scheme-handler/https" = ["url-handler.desktop"];
+        "x-scheme-handler/about" = ["url-handler.desktop"];
+        "x-scheme-handler/unknown" = ["url-handler.desktop"];
       };
     };
     desktopEntries = {
       url-handler = {
         name = "url-handler";
         genericName = "Web Browser";
-        exec = "${lib.getExe url_handler} %u";
+        exec = "${lib.getExe url-handler} %u";
         terminal = false;
         categories = [
           "Application"
diff --git a/modules/home.legacy/conf/xdg/url_handler.sh b/modules/home.legacy/conf/xdg/url-handler.sh
index f15df384..f15df384 100755
--- a/modules/home.legacy/conf/xdg/url_handler.sh
+++ b/modules/home.legacy/conf/xdg/url-handler.sh
diff --git a/pkgs/by-name/ts/tskm/Cargo.lock b/pkgs/by-name/ts/tskm/Cargo.lock
index 4a064858..68823d3c 100644
--- a/pkgs/by-name/ts/tskm/Cargo.lock
+++ b/pkgs/by-name/ts/tskm/Cargo.lock
@@ -3,6 +3,24 @@
 version = 4
 
 [[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
 name = "android-tzdata"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -92,6 +110,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
 
 [[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
 name = "cc"
 version = "1.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -116,6 +140,7 @@ dependencies = [
  "iana-time-zone",
  "js-sys",
  "num-traits",
+ "serde",
  "wasm-bindgen",
  "windows-link",
 ]
@@ -182,6 +207,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
 name = "dirs"
 version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -214,6 +248,28 @@ dependencies = [
 ]
 
 [[package]]
+name = "fallible-iterator"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
+[[package]]
+name = "flate2"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
 name = "form_urlencoded"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -230,7 +286,37 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashlink"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
+dependencies = [
+ "hashbrown",
 ]
 
 [[package]]
@@ -458,6 +544,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
 name = "litemap"
 version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -485,6 +581,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
+name = "miniz_oxide"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
 name = "num-traits"
 version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -512,6 +617,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
 name = "proc-macro2"
 version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -530,17 +641,37 @@ dependencies = [
 ]
 
 [[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
 name = "redox_users"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
  "libredox",
  "thiserror",
 ]
 
 [[package]]
+name = "rusqlite"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
+dependencies = [
+ "bitflags",
+ "fallible-iterator",
+ "fallible-streaming-iterator",
+ "hashlink",
+ "libsqlite3-sys",
+ "smallvec",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -637,6 +768,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
+name = "strum"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
+
+[[package]]
+name = "strum_macros"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
 name = "syn"
 version = "2.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -659,6 +809,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "taskchampion"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b010f5ebe51e88ae490691ed2a43b699e3468c8e3838e244accd8526aca7751b"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "chrono",
+ "flate2",
+ "log",
+ "rusqlite",
+ "serde",
+ "serde_json",
+ "strum",
+ "strum_macros",
+ "thiserror",
+ "uuid",
+]
+
+[[package]]
 name = "termcolor"
 version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -720,6 +890,7 @@ dependencies = [
  "serde",
  "serde_json",
  "stderrlog",
+ "taskchampion",
  "url",
  "walkdir",
 ]
@@ -771,6 +942,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
+name = "uuid"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+dependencies = [
+ "getrandom 0.3.2",
+ "serde",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
 name = "walkdir"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -787,6 +980,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
 name = "wasm-bindgen"
 version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -942,6 +1144,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
 name = "write16"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -978,6 +1189,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "zerofrom"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml
index d2990b0c..41fc5888 100644
--- a/pkgs/by-name/ts/tskm/Cargo.toml
+++ b/pkgs/by-name/ts/tskm/Cargo.toml
@@ -14,6 +14,7 @@ lz4_flex = "0.11.3"
 serde = { version = "1.0.219", features = ["derive"] }
 serde_json = "1.0.140"
 stderrlog = "0.6.0"
+taskchampion = { version = "2.0.3", default-features = false }
 url = { version = "2.5.4", features = ["serde"] }
 walkdir = "2.5.0"
 
@@ -74,6 +75,8 @@ style = { level = "warn", priority = -1 }
 complexity = { level = "warn", priority = -1 }
 perf = { level = "warn", priority = -1 }
 pedantic = { level = "warn", priority = -1 }
+missing_panics_doc = "allow"
+missing_errors_doc = "allow"
 
 [build-dependencies]
 anyhow = "1.0.97"
@@ -83,5 +86,6 @@ dirs = "6.0.0"
 log = "0.4.27"
 serde = { version = "1.0.219", features = ["derive"] }
 serde_json = "1.0.140"
+taskchampion = { version = "2.0.3", default-features = false }
 url = "2.5.4"
 walkdir = "2.5.0"
diff --git a/pkgs/by-name/ts/tskm/build.rs b/pkgs/by-name/ts/tskm/build.rs
index 8dfb213b..e3b60bb9 100644
--- a/pkgs/by-name/ts/tskm/build.rs
+++ b/pkgs/by-name/ts/tskm/build.rs
@@ -12,6 +12,9 @@ use crate::cli::CliArgs;
 pub mod task {
     include!("src/task/mod.rs");
 }
+pub mod state {
+    include!("src/state.rs");
+}
 
 pub mod interface {
     pub mod input {
diff --git a/pkgs/by-name/ts/tskm/flake.nix b/pkgs/by-name/ts/tskm/flake.nix
index 4b1142d6..5a5f628b 100644
--- a/pkgs/by-name/ts/tskm/flake.nix
+++ b/pkgs/by-name/ts/tskm/flake.nix
@@ -10,6 +10,10 @@
     pkgs = nixpkgs.legacyPackages."${system}";
   in {
     devShells."${system}".default = pkgs.mkShell {
+      buildInputs = [
+        pkgs.sqlite
+      ];
+
       packages = with pkgs; [
         cargo
         clippy
diff --git a/pkgs/by-name/ts/tskm/package.nix b/pkgs/by-name/ts/tskm/package.nix
index 1f206abf..3d320772 100644
--- a/pkgs/by-name/ts/tskm/package.nix
+++ b/pkgs/by-name/ts/tskm/package.nix
@@ -8,6 +8,7 @@
   git,
   rofi,
   firefox,
+  sqlite,
 }:
 rustPlatform.buildRustPackage (finalAttrs: {
   pname = "tskm";
@@ -27,6 +28,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
     git
     rofi
     firefox
+    sqlite
   ];
 
   nativeBuildInputs = [
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs
index 99c8693e..bd389ca5 100644
--- a/pkgs/by-name/ts/tskm/src/cli.rs
+++ b/pkgs/by-name/ts/tskm/src/cli.rs
@@ -1,9 +1,11 @@
 use std::path::PathBuf;
 
-use clap::{Parser, Subcommand};
+use anyhow::{bail, Result};
+use clap::{ArgAction, Parser, Subcommand};
 
 use crate::{
     interface::{input::Input, project::ProjectName},
+    state::State,
     task,
 };
 
@@ -21,6 +23,14 @@ use crate::{
 pub struct CliArgs {
     #[command(subcommand)]
     pub command: Command,
+
+    /// Increase message verbosity
+    #[arg(long="verbose", short = 'v', action = ArgAction::Count, default_value_t = 2)]
+    pub verbosity: u8,
+
+    /// Silence all output
+    #[arg(long, short = 'q')]
+    pub quiet: bool,
 }
 
 #[derive(Subcommand, Debug)]
@@ -66,7 +76,21 @@ pub enum ProjectCommand {
 #[derive(Subcommand, Debug, Clone, Copy)]
 pub enum NeorgCommand {
     /// Open the `neorg` project associated with id of the task.
-    Task { id: task::Id },
+    Task {
+        /// The working set id of the task
+        #[arg(value_parser = task_from_working_set_id)]
+        id: task::Task,
+    },
+}
+
+fn task_from_working_set_id(id: &str) -> Result<task::Task> {
+    let id: usize = id.parse()?;
+    let mut state = State::new_ro()?;
+
+    let Some(task) = task::Task::from_working_set(id, &mut state)? else {
+        bail!("Working set id '{id}' is not valid!")
+    };
+    Ok(task)
 }
 
 #[derive(Subcommand, Debug)]
diff --git a/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
index a9a46ee7..577de02c 100644
--- a/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
@@ -1,33 +1,46 @@
 use std::{
     env,
-    fs::{self, read_to_string, OpenOptions},
+    fs::{self, read_to_string, File, OpenOptions},
     io::Write,
     process::Command,
 };
 
-use anyhow::{bail, Result};
+use anyhow::{bail, Context, Result};
 
-use crate::cli::NeorgCommand;
+use crate::{cli::NeorgCommand, state::State};
 
-pub fn handle(command: NeorgCommand) -> Result<()> {
+pub fn handle(command: NeorgCommand, state: &mut State) -> Result<()> {
     match command {
         NeorgCommand::Task { id } => {
-            let project = id.project()?;
+            let project = id.project(state)?;
             let path = dirs::data_local_dir()
                 .expect("This should exists")
-                .join("notes")
+                .join("tskm/notes")
                 .join(project.get_neorg_path()?);
 
             fs::create_dir_all(path.parent().expect("This should exist"))?;
 
             {
-                let contents = read_to_string(&path)?;
-                if contents.contains(format!("% {}", id.to_uuid()?).as_str()) {
+                let contents = if path.exists() {
+                    read_to_string(&path)
+                        .with_context(|| format!("Failed to read file: '{}'", path.display()))?
+                } else {
+                    File::create(&path)
+                        .with_context(|| format!("Failed to create file: '{}'", path.display()))?;
+                    String::new()
+                };
+
+                if !contents.contains(format!("% {}", id.uuid()).as_str()) {
                     let mut options = OpenOptions::new();
-                    options.append(true).create(true);
+                    options.append(true).create(false);
 
                     let mut file = options.open(&path)?;
-                    file.write_all(format!("* TITLE (% {})", id.to_uuid()?).as_bytes())?;
+                    file.write_all(format!("* TITLE (% {})", id.uuid()).as_bytes())
+                        .with_context(|| {
+                            format!("Failed to write task uuid to file: '{}'", path.display())
+                        })?;
+                    file.flush()
+                        .with_context(|| format!("Failed to flush file: '{}'", path.display()))?;
                 }
             }
 
@@ -36,7 +49,7 @@ pub fn handle(command: NeorgCommand) -> Result<()> {
                 .args([
                     path.to_str().expect("Should be a utf-8 str"),
                     "-c",
-                    format!("/% {}", id.to_uuid()?).as_str(),
+                    format!("/% {}", id.uuid()).as_str(),
                 ])
                 .status()?;
             if !status.success() {
@@ -46,7 +59,7 @@ pub fn handle(command: NeorgCommand) -> Result<()> {
             {
                 let status = Command::new("git")
                     .args(["add", "."])
-                    .current_dir(&path)
+                    .current_dir(path.parent().expect("Will exist"))
                     .status()?;
                 if !status.success() {
                     bail!("Git add . failed!");
@@ -63,7 +76,7 @@ pub fn handle(command: NeorgCommand) -> Result<()> {
                         .as_str(),
                         "--no-gpg-sign",
                     ])
-                    .current_dir(&path)
+                    .current_dir(path.parent().expect("Will exist"))
                     .status()?;
                 if !status.success() {
                     bail!("Git commit failed!");
@@ -71,7 +84,7 @@ pub fn handle(command: NeorgCommand) -> Result<()> {
             }
 
             {
-                id.annotate("[neorg data]")?;
+                id.mark_neorg_data(state)?;
             }
         }
     }
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
index dc0d165d..0b565abd 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
@@ -3,15 +3,15 @@ use std::process;
 use anyhow::{bail, Context, Result};
 use log::{error, info};
 
-use crate::{cli::OpenCommand, rofi, task};
+use crate::{cli::OpenCommand, rofi, state::State, task};
 
-pub fn handle(command: OpenCommand) -> Result<()> {
+pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
     match command {
         OpenCommand::Review => {
             for project in task::Project::all().context("Failed to get all project files")? {
                 if project.is_touched() {
                     info!("Reviewing project: '{}'", project.to_project_display());
-                    open_in_browser(project).with_context(|| {
+                    open_in_browser(project, state).with_context(|| {
                         format!(
                             "Failed to open project ('{}') in Firefox",
                             project.to_project_display()
@@ -38,7 +38,7 @@ pub fn handle(command: OpenCommand) -> Result<()> {
             };
 
             project.touch().context("Failed to touch project")?;
-            open_in_browser(&project).with_context(|| {
+            open_in_browser(&project, state).with_context(|| {
                 format!("Failed to open project: {}", project.to_project_display())
             })?;
         }
@@ -60,7 +60,7 @@ pub fn handle(command: OpenCommand) -> Result<()> {
                 .touch()
                 .context("Failed to touch project")?;
 
-            open_in_browser(&selected_project).context("Failed to open project")?;
+            open_in_browser(&selected_project, state).context("Failed to open project")?;
         }
         OpenCommand::ListTabs { project } => {
             let project = if let Some(p) = project {
@@ -109,12 +109,11 @@ pub fn handle(command: OpenCommand) -> Result<()> {
     Ok(())
 }
 
-fn open_in_browser(selected_project: &task::Project) -> Result<()> {
+fn open_in_browser(selected_project: &task::Project, state: &mut State) -> Result<()> {
     let old_project: Option<task::Project> =
         task::Project::get_current().context("Failed to get currently active project")?;
-    // We have ensured that only one task may be active
-    let old_task: Option<task::Id> =
-        task::Id::get_current().context("Failed to get currently active task")?;
+    let old_task: Option<task::Task> =
+        task::Task::get_current(state).context("Failed to get currently active task")?;
 
     selected_project.activate().with_context(|| {
         format!(
@@ -124,7 +123,7 @@ fn open_in_browser(selected_project: &task::Project) -> Result<()> {
     })?;
 
     let tracking_task = {
-        let all_tasks = selected_project.get_tasks().with_context(|| {
+        let all_tasks = selected_project.get_tasks(state).with_context(|| {
             format!(
                 "Failed to get assoctiated tasks for project: '{}'",
                 selected_project.to_project_display()
@@ -132,7 +131,7 @@ fn open_in_browser(selected_project: &task::Project) -> Result<()> {
         })?;
 
         let tracking_task = all_tasks.into_iter().find(|t| {
-            let maybe_desc = t.description();
+            let maybe_desc = t.description(state);
             if let Ok(desc) = maybe_desc {
                 desc == "tracking"
             } else {
@@ -149,7 +148,7 @@ fn open_in_browser(selected_project: &task::Project) -> Result<()> {
                 "Starting task {} -> tracking",
                 selected_project.to_project_display()
             );
-            task.start()
+            task.start(state)
                 .with_context(|| format!("Failed to start task {task}"))?;
         }
         tracking_task
@@ -169,11 +168,11 @@ fn open_in_browser(selected_project: &task::Project) -> Result<()> {
     }
 
     if let Some(task) = tracking_task {
-        task.stop()
+        task.stop(state)
             .with_context(|| format!("Failed to stop task {task}"))?;
     }
     if let Some(task) = old_task {
-        task.start()
+        task.start(state)
             .with_context(|| format!("Failed to start task {task}"))?;
     }
 
diff --git a/pkgs/by-name/ts/tskm/src/main.rs b/pkgs/by-name/ts/tskm/src/main.rs
index 7fc9c0d4..f4416c6d 100644
--- a/pkgs/by-name/ts/tskm/src/main.rs
+++ b/pkgs/by-name/ts/tskm/src/main.rs
@@ -1,14 +1,13 @@
-#![allow(clippy::missing_panics_doc)]
-#![allow(clippy::missing_errors_doc)]
-
 use anyhow::Result;
 use clap::Parser;
+use state::State;
 
 use crate::interface::{input, neorg, open, project};
 
 pub mod cli;
 pub mod interface;
 pub mod rofi;
+pub mod state;
 pub mod task;
 
 use crate::cli::{CliArgs, Command};
@@ -47,18 +46,20 @@ fn main() -> Result<(), anyhow::Error> {
 
     stderrlog::new()
         .module(module_path!())
-        .quiet(false)
+        .quiet(args.quiet)
         .show_module_names(true)
         .color(stderrlog::ColorChoice::Auto)
-        .verbosity(5)
+        .verbosity(usize::from(args.verbosity))
         .timestamp(stderrlog::Timestamp::Off)
         .init()
         .expect("Let's just hope that this does not panic");
 
+    let mut state = State::new_rw()?;
+
     match args.command {
         Command::Inputs { command } => input::handle(command)?,
-        Command::Neorg { command } => neorg::handle(command)?,
-        Command::Open { command } => open::handle(command)?,
+        Command::Neorg { command } => neorg::handle(command, &mut state)?,
+        Command::Open { command } => open::handle(command, &mut state)?,
         Command::Projects { command } => project::handle(command)?,
     }
 
diff --git a/pkgs/by-name/ts/tskm/src/state.rs b/pkgs/by-name/ts/tskm/src/state.rs
new file mode 100644
index 00000000..175a7f03
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/state.rs
@@ -0,0 +1,45 @@
+use std::path::PathBuf;
+
+use anyhow::Result;
+use taskchampion::{storage::AccessMode, Replica, StorageConfig};
+
+pub struct State {
+    replica: Replica,
+}
+
+impl std::fmt::Debug for State {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "State")
+    }
+}
+
+impl State {
+    fn taskdb_dir() -> PathBuf {
+        dirs::data_local_dir().expect("Should exist").join("task")
+    }
+
+    fn new(taskdb_dir: PathBuf, access_mode: AccessMode) -> Result<Self> {
+        let storage = StorageConfig::OnDisk {
+            taskdb_dir,
+            create_if_missing: false,
+            access_mode,
+        }
+        .into_storage()?;
+
+        let replica = Replica::new(storage);
+
+        Ok(Self { replica })
+    }
+
+    pub fn new_ro() -> Result<Self> {
+        Self::new(Self::taskdb_dir(), AccessMode::ReadOnly)
+    }
+    pub fn new_rw() -> Result<Self> {
+        Self::new(Self::taskdb_dir(), AccessMode::ReadWrite)
+    }
+
+    #[must_use]
+    pub fn replica(&mut self) -> &mut Replica {
+        &mut self.replica
+    }
+}
diff --git a/pkgs/by-name/ts/tskm/src/task/mod.rs b/pkgs/by-name/ts/tskm/src/task/mod.rs
index c3a6d614..03a12faa 100644
--- a/pkgs/by-name/ts/tskm/src/task/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/task/mod.rs
@@ -9,109 +9,136 @@ use std::{
 
 use anyhow::{bail, Context, Result};
 use log::{debug, info, trace};
+use taskchampion::Tag;
 
-use crate::interface::project::ProjectName;
+use crate::{interface::project::ProjectName, state::State};
 
 /// The `taskwarrior` id of a task.
 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
-pub struct Id {
-    id: u64,
+pub struct Task {
+    uuid: taskchampion::Uuid,
 }
-impl Id {
-    /// # Errors
-    /// When `task` execution fails
-    pub fn get_current() -> Result<Option<Self>> {
-        // We have ensured that only one task may be active
-        let self_str = run_task(&["+ACTIVE", "_ids"])?;
 
-        if self_str.is_empty() {
-            Ok(None)
+impl From<&taskchampion::Task> for Task {
+    fn from(value: &taskchampion::Task) -> Self {
+        Self {
+            uuid: value.get_uuid(),
+        }
+    }
+}
+impl From<&taskchampion::TaskData> for Task {
+    fn from(value: &taskchampion::TaskData) -> Self {
+        Self {
+            uuid: value.get_uuid(),
+        }
+    }
+}
+
+impl Task {
+    pub fn from_working_set(id: usize, state: &mut State) -> Result<Option<Self>> {
+        Ok(state
+            .replica()
+            .working_set()?
+            .by_index(id)
+            .map(|uuid| Self { uuid }))
+    }
+
+    pub fn get_current(state: &mut State) -> Result<Option<Self>> {
+        let tasks = state
+            .replica()
+            .pending_tasks()?
+            .into_iter()
+            .filter(taskchampion::Task::is_active)
+            .collect::<Vec<_>>();
+
+        assert!(
+            tasks.len() <= 1,
+            "We have ensured that only one task may be active, via a hook"
+        );
+        if let Some(active) = tasks.first() {
+            Ok(Some(Self::from(active)))
         } else {
-            Self::from_str(&self_str).map(Some)
+            Ok(None)
         }
     }
 
-    /// # Errors
-    /// When `task` execution fails
-    pub fn to_uuid(&self) -> Result<String> {
-        let uuid = run_task(&[self.to_string().as_str(), "uuids"])?;
+    #[must_use]
+    pub fn uuid(&self) -> &taskchampion::Uuid {
+        &self.uuid
+    }
 
-        Ok(uuid)
+    fn as_task(&self, state: &mut State) -> Result<taskchampion::Task> {
+        Ok(state
+            .replica()
+            .get_task(self.uuid)?
+            .expect("We have the task from this replica, it should still be in it"))
     }
 
-    /// # Panics
-    /// When internal assertions fail.
-    /// # Errors
-    /// When `task` execution fails
-    pub fn annotate(&self, message: &str) -> Result<()> {
-        run_task(&["annotate", self.to_string().as_str(), "--", message])?;
+    /// Adds a tag to the task, to show the user that it has additional neorg data.
+    pub fn mark_neorg_data(&self, state: &mut State) -> Result<()> {
+        let mut ops = vec![];
+        self.as_task(state)?
+            .add_tag(&Tag::from_str("neorg_data").expect("Is valid"), &mut ops)?;
+        state.replica().commit_operations(ops)?;
         Ok(())
     }
 
-    /// # Panics
-    /// When internal assertions fail.
-    /// # Errors
-    /// When `task` execution fails
-    pub fn start(&self) -> Result<()> {
+    /// Try to start this task.
+    /// It will stop previously active tasks.
+    pub fn start(&self, state: &mut State) -> Result<()> {
         info!("Activating {self}");
 
-        let output = run_task(&["start", self.to_string().as_str()])?;
-        assert!(output.is_empty());
+        if let Some(active) = Self::get_current(state)? {
+            active.stop(state)?;
+        }
+
+        let mut ops = vec![];
+        self.as_task(state)?.start(&mut ops)?;
+        state.replica().commit_operations(ops)?;
         Ok(())
     }
-    /// # Panics
-    /// When internal assertions fail.
-    /// # Errors
-    /// When `task` execution fails
-    pub fn stop(&self) -> Result<()> {
+
+    /// Stops this task.
+    pub fn stop(&self, state: &mut State) -> Result<()> {
         info!("Stopping {self}");
 
-        let output = run_task(&["stop", self.to_string().as_str()])?;
-        assert!(output.is_empty());
+        let mut ops = vec![];
+        self.as_task(state)?.stop(&mut ops)?;
+        state.replica().commit_operations(ops)?;
         Ok(())
     }
 
-    /// # Panics
-    /// When internal assertions fail.
-    /// # Errors
-    /// When `task` execution fails
-    pub fn description(&self) -> Result<String> {
-        let output = run_task(&["rc.context=none", "_zshids", self.to_string().as_str()])?;
-        let (id, desc) = output
-            .split_once(':')
-            .expect("The output should always contain one colon");
-        assert_eq!(id.parse::<Id>().expect("This should be a valid id"), *self);
-        Ok(desc.to_owned())
+    pub fn description(&self, state: &mut State) -> Result<String> {
+        Ok(self.as_task(state)?.get_description().to_owned())
     }
 
-    /// # Panics
-    /// When internal assertions fail.
-    /// # Errors
-    /// When `task` execution fails
-    pub fn project(&self) -> Result<Project> {
-        let output = run_task(&[
-            "rc.context=none",
-            "_get",
-            format!("{self}.project").as_str(),
-        ])?;
+    pub fn project(&self, state: &mut State) -> Result<Project> {
+        let output = {
+            let task = self.as_task(state)?;
+            let task_data = task.into_task_data();
+            task_data
+                .get("project")
+                .expect("Every task should have a project")
+                .to_owned()
+        };
         let project = Project::from_project_string(output.as_str())
             .expect("This comes from tw, it should be valid");
         Ok(project)
     }
 }
 
-impl Display for Id {
+impl Display for Task {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.id.fmt(f)
+        self.uuid.fmt(f)
     }
 }
 
-impl FromStr for Id {
+impl FromStr for Task {
     type Err = anyhow::Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let id = u64::from_str(s)?;
-        Ok(Self { id })
+        let uuid = taskchampion::Uuid::from_str(s)?;
+        Ok(Self { uuid })
     }
 }
 
@@ -259,21 +286,14 @@ impl Project {
 
     /// # Errors
     /// When `task` execution fails.
-    pub fn get_tasks(&self) -> Result<Vec<Id>> {
-        let output = run_task(&[
-            "rc.context=none",
-            format!("project:{}", self.to_project_display()).as_str(),
-            "_ids",
-        ])?;
-
-        if output.is_empty() {
-            Ok(vec![])
-        } else {
-            output
-                .lines()
-                .map(Id::from_str)
-                .collect::<Result<Vec<Id>>>()
-        }
+    pub fn get_tasks(&self, state: &mut State) -> Result<Vec<Task>> {
+        Ok(state
+            .replica()
+            .pending_task_data()?
+            .into_iter()
+            .filter(|t| t.get("project").expect("Is set") == self.to_project_display())
+            .map(|t| Task::from(&t))
+            .collect())
     }
 
     /// # Errors