this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'main' of git.sealight.xyz:aynish/helm

+597 -231
+21 -70
flake.lock
··· 43 43 "inputs": { 44 44 "devenv": "devenv", 45 45 "fenix": "fenix", 46 - "flake-parts": "flake-parts", 46 + "flake-parts": [ 47 + "flake-parts" 48 + ], 47 49 "flake-root": "flake-root", 48 50 "mk-shell-bin": "mk-shell-bin", 49 51 "nix2container": "nix2container", ··· 381 383 "narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=", 382 384 "rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1", 383 385 "type": "tarball", 384 - "url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1" 386 + "url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz" 385 387 }, 386 388 "original": { 387 389 "type": "tarball", ··· 393 395 "nixpkgs-lib": "nixpkgs-lib" 394 396 }, 395 397 "locked": { 396 - "lastModified": 1698882062, 397 - "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", 398 - "path": "/nix/store/vf5ra8smwlqy3pi0300s580g5msc3xp7-source", 399 - "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", 400 - "type": "path" 398 + "lastModified": 1772408722, 399 + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", 400 + "owner": "hercules-ci", 401 + "repo": "flake-parts", 402 + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", 403 + "type": "github" 401 404 }, 402 405 "original": { 403 - "id": "flake-parts", 404 - "type": "indirect" 406 + "owner": "hercules-ci", 407 + "repo": "flake-parts", 408 + "type": "github" 405 409 } 406 410 }, 407 411 "flake-parts_2": { ··· 819 823 "type": "github" 820 824 } 821 825 }, 822 - "learning-goal": { 823 - "flake": false, 824 - "locked": { 825 - "lastModified": 1772311752, 826 - "narHash": "sha256-lvQdDRuC9H+8F4Fud753c6NogVdZtGqgtjKbiSVZCig=", 827 - "owner": "DrCatHicks", 828 - "repo": "learning-goal", 829 - "rev": "cc7e3a6c7f0917501f1fd422bad81ab6f5040050", 830 - "type": "github" 831 - }, 832 - "original": { 833 - "owner": "DrCatHicks", 834 - "repo": "learning-goal", 835 - "type": "github" 836 - } 837 - }, 838 - "learning-opportunities": { 839 - "flake": false, 840 - "locked": { 841 - "lastModified": 1773158564, 842 - "narHash": "sha256-xMpy9XxMaNCIAOr2dffrc5dyRt56jlam+XQjrNapsEw=", 843 - "owner": "DrCatHicks", 844 - "repo": "learning-opportunities", 845 - "rev": "e5f985d376461993253d285096ed0f4b4a095858", 846 - "type": "github" 847 - }, 848 - "original": { 849 - "owner": "DrCatHicks", 850 - "repo": "learning-opportunities", 851 - "type": "github" 852 - } 853 - }, 854 826 "llm-agents": { 855 827 "inputs": { 856 828 "blueprint": "blueprint", ··· 1075 1047 }, 1076 1048 "nixpkgs-lib": { 1077 1049 "locked": { 1078 - "dir": "lib", 1079 - "lastModified": 1698611440, 1080 - "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", 1081 - "owner": "NixOS", 1082 - "repo": "nixpkgs", 1083 - "rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", 1050 + "lastModified": 1772328832, 1051 + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", 1052 + "owner": "nix-community", 1053 + "repo": "nixpkgs.lib", 1054 + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", 1084 1055 "type": "github" 1085 1056 }, 1086 1057 "original": { 1087 - "dir": "lib", 1088 - "owner": "NixOS", 1089 - "ref": "nixos-unstable", 1090 - "repo": "nixpkgs", 1058 + "owner": "nix-community", 1059 + "repo": "nixpkgs.lib", 1091 1060 "type": "github" 1092 1061 } 1093 1062 }, ··· 1222 1191 "type": "github" 1223 1192 } 1224 1193 }, 1225 - "opencode-handoff": { 1226 - "flake": false, 1227 - "locked": { 1228 - "lastModified": 1772630975, 1229 - "narHash": "sha256-1wDwwz7gcKLeCr0kqwQtQi7UWf12AYiPDL8YT9iFO08=", 1230 - "owner": "Chickensoupwithrice", 1231 - "repo": "opencode-handoff", 1232 - "rev": "b18d546e567c8c15c7ce8377f82f1b81cd838890", 1233 - "type": "github" 1234 - }, 1235 - "original": { 1236 - "owner": "Chickensoupwithrice", 1237 - "repo": "opencode-handoff", 1238 - "type": "github" 1239 - } 1240 - }, 1241 1194 "poonam": { 1242 1195 "inputs": { 1243 1196 "nixpkgs": [ ··· 1297 1250 "darwin": "darwin_2", 1298 1251 "deploy-rs": "deploy-rs", 1299 1252 "disko": "disko", 1253 + "flake-parts": "flake-parts", 1300 1254 "grasp": "grasp", 1301 1255 "hardware": "hardware", 1302 1256 "home-manager": "home-manager_2", ··· 1304 1258 "homebrew-cask": "homebrew-cask", 1305 1259 "homebrew-core": "homebrew-core", 1306 1260 "jovian": "jovian", 1307 - "learning-goal": "learning-goal", 1308 - "learning-opportunities": "learning-opportunities", 1309 1261 "llm-agents": "llm-agents", 1310 1262 "nix-homebrew": "nix-homebrew", 1311 1263 "nix-matrix-appservices": "nix-matrix-appservices", 1312 1264 "nixos-hardware": "nixos-hardware", 1313 1265 "nixpkgs": "nixpkgs_5", 1314 1266 "nur": "nur", 1315 - "opencode-handoff": "opencode-handoff", 1316 1267 "poonam": "poonam", 1317 1268 "rust-overlay": "rust-overlay", 1318 1269 "tangled": "tangled",
+9 -14
flake.nix
··· 77 77 78 78 llm-agents.url = "github:numtide/llm-agents.nix"; 79 79 80 - opencode-handoff = { 81 - url = "github:Chickensoupwithrice/opencode-handoff"; 82 - flake = false; 83 - }; 84 - learning-opportunities = { 85 - url = "github:DrCatHicks/learning-opportunities"; 86 - flake = false; 87 - }; 88 - learning-goal = { 89 - url = "github:DrCatHicks/learning-goal"; 90 - flake = false; 91 - }; 92 - 93 80 # Others 94 81 nur.url = "github:nix-community/NUR"; 95 82 rust-overlay = { ··· 98 85 }; 99 86 tidalcycles.url = "github:mitchmindtree/tidalcycles.nix"; 100 87 tidalcycles.inputs.nixpkgs.follows = "unstable"; 88 + flake-parts.url = "github:hercules-ci/flake-parts"; 101 89 autohide-tdrop = { 102 90 url = "github:I-Want-ToBelieve/autohide-tdrop"; 103 91 inputs.nixpkgs.follows = "nixpkgs"; 92 + inputs.flake-parts.follows = "flake-parts"; 104 93 }; 105 94 # TODO hundred rabbits software 106 95 # TODO needs secrets ··· 169 158 pkg: 170 159 builtins.elem (nixpkgs.lib.getName pkg) [ 171 160 "ripcord" 161 + "obsidian" 172 162 "vcv-rack" 173 163 "SunVox" 174 164 "renoise" ··· 284 274 system: 285 275 let 286 276 pkgs = nixpkgsFor.${system}; 277 + allPkgs = import ./pkgs { pkgs = pkgs; }; 287 278 in 288 - import ./pkgs { pkgs = pkgs; } 279 + # Filter out non-derivation attributes (e.g. obsidianPlugins is an attrset) 280 + nixpkgs.lib.filterAttrs (_: v: nixpkgs.lib.isDerivation v) allPkgs 289 281 ); 290 282 # Devshell for bootstrapping 291 283 # Acessible through 'nix develop' or 'nix-shell' (legacy) ··· 326 318 home-manager.useGlobalPkgs = true; 327 319 home-manager.useUserPackages = true; 328 320 home-manager.extraSpecialArgs = { inherit inputs; }; 321 + home-manager.backupFileExtension = "hm-bak"; 329 322 home-manager.users.anish = import ./home/gui; 330 323 } 331 324 ]; ··· 365 358 { 366 359 home-manager.useGlobalPkgs = true; 367 360 home-manager.useUserPackages = true; 361 + home-manager.backupFileExtension = "hm-bak"; 368 362 home-manager.users.anish = import ./home/core; 369 363 } 370 364 ]; ··· 391 385 home-manager.useGlobalPkgs = true; 392 386 home-manager.useUserPackages = true; 393 387 home-manager.extraSpecialArgs = { inherit inputs; }; 388 + home-manager.backupFileExtension = "hm-bak"; 394 389 home-manager.users.anish = import ./home/dev; 395 390 } 396 391 ];
+1 -1
home/profiles/cli/default.nix
··· 29 29 skim 30 30 tealdeer 31 31 usbutils 32 - utillinux 32 + util-linux 33 33 whois 34 34 iftop 35 35 wget
+31 -21
home/profiles/desktop/default.nix
··· 1 - { pkgs, config, lib, ... }: 1 + { 2 + pkgs, 3 + config, 4 + lib, 5 + ... 6 + }: 2 7 let 3 8 dracula-gtk = pkgs.fetchFromGitHub { 4 9 owner = "dracula"; ··· 8 13 }; 9 14 in 10 15 { 16 + imports = [ ../obsidian ]; 17 + 11 18 home.pointerCursor = { 12 19 x11.enable = true; 13 20 x11.defaultCursor = "left_ptr"; ··· 33 40 termpdfpy 34 41 # libsForQt5.kontact 35 42 thunderbird 36 - libsecret # For secret-tool to manage keyring 43 + libsecret # For secret-tool to manage keyring 37 44 blender 38 45 ]; 39 46 ··· 48 55 xdg.enable = true; # This doesn't seem to do anything so we have below 49 56 xdg.configHome = "/home/anish/.config"; # TODO bad hardcode 50 57 xdg.configFile = { 51 - "Kvantum/Dracula/Dracula.kvconfig".source = "${dracula-gtk}/kde/kvantum/Dracula-purple-solid/Dracula-purple-solid.kvconfig"; 52 - "Kvantum/Dracula/Dracula.svg".source = "${dracula-gtk}/kde/kvantum/Dracula-purple-solid/Dracula-purple-solid.svg"; 58 + "Kvantum/Dracula/Dracula.kvconfig".source = 59 + "${dracula-gtk}/kde/kvantum/Dracula-purple-solid/Dracula-purple-solid.kvconfig"; 60 + "Kvantum/Dracula/Dracula.svg".source = 61 + "${dracula-gtk}/kde/kvantum/Dracula-purple-solid/Dracula-purple-solid.svg"; 53 62 "Kvantum/kvantum.kvconfig".text = "[General]\ntheme=Dracula"; 54 63 }; 55 64 xdg.mimeApps.defaultApplications = { ··· 122 131 }; 123 132 }; 124 133 125 - 126 134 qt = { 127 135 enable = true; 128 136 platformTheme.name = "gtk"; 129 - style = { name = "qt5ct-style"; }; 137 + style = { 138 + name = "qt5ct-style"; 139 + }; 130 140 }; 131 141 132 142 # set up env ··· 139 149 # Black 140 150 "*.color0" = "#1F2430"; 141 151 "*.color8" = "#F28779"; 142 - # Red 152 + # Red 143 153 "*.color1" = "#A6CC70"; 144 154 "*.color9" = "#FFCC66"; 145 - # Green 155 + # Green 146 156 "*.color2" = "#5CCFE6"; 147 157 "*.color10" = "#F29E74"; 148 158 # Yellow 149 159 "*.color3" = "#77A8D9"; 150 160 "*.color11" = "#5C6773"; 151 - # Blue 161 + # Blue 152 162 "*.color4" = "#707A8C"; 153 163 "*.color12" = "#F27983"; 154 - # Magenta 164 + # Magenta 155 165 "*.color5" = "#BAE67E"; 156 166 "*.color13" = "#FFD580"; 157 - # Cyan 167 + # Cyan 158 168 "*.color6" = "#73D0FF"; 159 169 "*.color14" = "#FFA759"; 160 - # White 170 + # White 161 171 "*.color7" = "#95E6CB"; 162 172 "*.color15" = "#CBCCC6"; 163 173 # Bold, Italic, Underline ··· 178 188 # shadow = true; 179 189 # shadowOffsets = [ (-10) (-10) ]; 180 190 # shadowOpacity = "0.22"; 181 - # # activeOpacity = "1.00"; 182 - # # inactiveOpacity = "0.92"; 183 - # extraOptions = '' 184 - # shadow-radius = 12; 185 - # # blur-background = true; 186 - # # blur-background-frame = true; 187 - # # blur-background-fixed = true; 188 - # blur-kern = "7x7box"; 189 - # blur-strength = 320; 191 + # # activeOpacity = "1.00"; 192 + # # inactiveOpacity = "0.92"; 193 + # extraOptions = '' 194 + # shadow-radius = 12; 195 + # # blur-background = true; 196 + # # blur-background-frame = true; 197 + # # blur-background-fixed = true; 198 + # blur-kern = "7x7box"; 199 + # blur-strength = 320; 190 200 # corner-radius = 3 191 201 # ''; 192 202 # };
-73
home/profiles/obsidian/.obsidian.vimrc
··· 1 - " ============================================================ 2 - " Obsidian Vimrc (requires Vimrc Support plugin by esm7) 3 - " ============================================================ 4 - 5 - " --- Clipboard --- 6 - " All yanks go to system clipboard 7 - set clipboard=unnamedplus 8 - 9 - " Make Y yank to end of line (consistent with C and D) 10 - nnoremap Y y$ 11 - 12 - " --- Unbind Space for use as leader --- 13 - unmap <Space> 14 - 15 - " --- Sidebar & Navigation --- 16 - " Toggle left sidebar 17 - exmap toggleSidebar obcommand app:toggle-left-sidebar 18 - nmap <Space>e :toggleSidebar<CR> 19 - 20 - " Reveal active file in file explorer 21 - exmap revealFile obcommand file-explorer:reveal-active-file 22 - nmap <Space>f :revealFile<CR> 23 - 24 - " Command palette 25 - exmap commandPalette obcommand command-palette:open 26 - nmap <Space>p :commandPalette<CR> 27 - 28 - " --- Tab Navigation --- 29 - " Next tab (matches Vim gt) 30 - exmap nextTab obcommand workspace:next-tab 31 - nmap gt :nextTab<CR> 32 - 33 - " Previous tab (matches Vim gT) 34 - exmap prevTab obcommand workspace:previous-tab 35 - nmap gT :prevTab<CR> 36 - 37 - " Close current tab 38 - exmap closeTab obcommand workspace:close 39 - nmap gd :closeTab<CR> 40 - 41 - " --- Notes --- 42 - " Daily note (Periodic Notes) 43 - exmap dailyNote obcommand periodic-notes:open-daily-note 44 - nmap <Space>d :dailyNote<CR> 45 - 46 - " Weekly note (Periodic Notes) 47 - exmap weeklyNote obcommand periodic-notes:open-weekly-note 48 - nmap <Space>w :weeklyNote<CR> 49 - 50 - " New Zettelkasten note 51 - exmap newZK obcommand zk-prefixer 52 - nmap <Space>zn :newZK<CR> 53 - 54 - " Split (QuickAdd macro) 55 - exmap splitNote obcommand quickadd:choice:1d350c80-9184-4f8a-b37e-4e7c0712a19f 56 - nmap <Space>zs :splitNote<CR> 57 - 58 - " --- Folding --- 59 - exmap unfoldAtCursor obcommand editor:unfold 60 - exmap foldAtCursor obcommand editor:fold 61 - exmap toggleFold obcommand editor:toggle-fold 62 - exmap unfoldAll obcommand editor:unfold-all 63 - exmap foldAll obcommand editor:fold-all 64 - 65 - nmap zo :unfoldAtCursor<CR> 66 - nmap zc :foldAtCursor<CR> 67 - nmap za :toggleFold<CR> 68 - nmap zR :unfoldAll<CR> 69 - nmap zM :foldAll<CR> 70 - 71 - " --- Quick Switcher --- 72 - exmap quickSwitcher obcommand switcher:open 73 - nmap <Space><Space> :quickSwitcher<CR>
+9 -3
home/profiles/obsidian/default.nix
··· 511 511 }; 512 512 }; 513 513 } 514 - plugins.obsidian-vimrc-support 514 + { 515 + pkg = plugins.obsidian-vimrc-support; 516 + settings = { 517 + supportJsCommands = true; 518 + }; 519 + } 515 520 plugins.obsidian-atmosphere 521 + plugins.obsidian-front-matter-title-plugin 516 522 ]; 517 523 518 524 themes = [ ··· 557 563 }; 558 564 }; 559 565 560 - # .obsidian.vimrc lives in the vault root, not inside .obsidian/ 561 566 # Using home.file with source for symlink-based management 562 567 home.file = { 563 - "kitaab/markdown/.obsidian.vimrc".source = ./.obsidian.vimrc; 568 + "kitaab/markdown/.obsidian.vimrc".source = ./obsidian.vimrc; 569 + "kitaab/markdown/scripts/follow-or-create.js".source = ./follow-or-create.js; 564 570 } 565 571 // lib.optionalAttrs isDarwin { 566 572 "usr/acreom/sourcegraph/.obsidian.vimrc".source = ./.obsidian.vimrc;
+204
home/profiles/obsidian/follow-or-create.js
··· 1 + // follow-or-create.js — obsidian-vimrc-support jsfile command 2 + // Args from plugin: editor, view, selection 3 + // Executed via Function() constructor — return value is NOT awaited. 4 + 5 + (async function() { 6 + try { 7 + var app = view.app; 8 + var activeFile = view.file; 9 + var sourcePath = activeFile ? activeFile.path : ""; 10 + var cur = editor.getCursor(); 11 + var line = editor.getLine(cur.line); 12 + 13 + var text = ""; 14 + var rangeFrom = null; 15 + var rangeTo = null; 16 + var insideWikilink = false; 17 + 18 + // --- Check if cursor is inside a [[wikilink]] --- 19 + var before = line.substring(0, cur.ch); 20 + var after = line.substring(cur.ch); 21 + var openIdx = before.lastIndexOf("[["); 22 + var closeInBefore = before.lastIndexOf("]]"); 23 + var closeIdx = after.indexOf("]]"); 24 + 25 + if (openIdx !== -1 && (closeInBefore === -1 || closeInBefore < openIdx) && closeIdx !== -1) { 26 + insideWikilink = true; 27 + var fullLink = line.substring(openIdx + 2, cur.ch + closeIdx); 28 + text = fullLink.split("|")[0].trim(); 29 + rangeFrom = { line: cur.line, ch: openIdx }; 30 + rangeTo = { line: cur.line, ch: cur.ch + closeIdx + 2 }; 31 + } 32 + 33 + // --- Visual mode: use the `selection` parameter from the plugin --- 34 + // The ':' keystroke collapses the editor selection before the ex-command 35 + // runs, so editor.getSelection() returns "". However, the plugin captures 36 + // the selection on cursorActivity (before collapse) and passes it as the 37 + // `selection` argument. We use it when anchor !== head. 38 + if (!insideWikilink && !text && selection && selection.anchor && selection.head) { 39 + var a = selection.anchor; 40 + var h = selection.head; 41 + if (a.line !== h.line || a.ch !== h.ch) { 42 + if (a.line < h.line || (a.line === h.line && a.ch <= h.ch)) { 43 + rangeFrom = { line: a.line, ch: a.ch }; 44 + rangeTo = { line: h.line, ch: h.ch }; 45 + } else { 46 + rangeFrom = { line: h.line, ch: h.ch }; 47 + rangeTo = { line: a.line, ch: a.ch }; 48 + } 49 + text = editor.getRange(rangeFrom, rangeTo).trim(); 50 + } 51 + } 52 + 53 + // --- Normal mode: word under cursor --- 54 + if (!insideWikilink && !text) { 55 + var isWordChar = function(c) { 56 + if (!c) return false; 57 + if (c === "'" || c === "\u2019") return true; // straight and curly apostrophe 58 + return /[\p{L}\p{N}_\-]/u.test(c); 59 + }; 60 + var start = cur.ch; 61 + var end = cur.ch; 62 + while (start > 0 && isWordChar(line[start - 1])) start--; 63 + while (end < line.length && isWordChar(line[end])) end++; 64 + text = line.substring(start, end); 65 + rangeFrom = { line: cur.line, ch: start }; 66 + rangeTo = { line: cur.line, ch: end }; 67 + } 68 + 69 + if (!text) return; 70 + 71 + // --- If inside a wikilink, just open it in a new tab --- 72 + if (insideWikilink) { 73 + await app.workspace.openLinkText(text, sourcePath, "tab"); 74 + return; 75 + } 76 + 77 + // --- Sanitize text for use in [[wikilink|alias]] syntax --- 78 + // Pipe and closing brackets would break the link. 79 + var safeAlias = text.replace(/\|/g, "-").replace(/\]\]/g, ")"); 80 + 81 + // --- Search for existing note by link path OR resolved title --- 82 + var foundFile = null; 83 + 84 + // First: try standard link-path resolution (handles basenames, paths, etc.) 85 + foundFile = app.metadataCache.getFirstLinkpathDest(text, sourcePath); 86 + 87 + // Second: use obsidian-front-matter-title plugin's resolver if available. 88 + // This respects the plugin's configured template (e.g. "title", "foo.bar") 89 + // so we don't hardcode which frontmatter key holds the display name. 90 + // Falls back to raw frontmatter.title if the plugin isn't installed. 91 + if (!foundFile) { 92 + var fmtPlugin = app.plugins.getPlugin("obsidian-front-matter-title-plugin"); 93 + var resolver = null; 94 + if (fmtPlugin && fmtPlugin.getDefer) { 95 + var defer = fmtPlugin.getDefer(); 96 + if (defer && defer.isPluginReady && defer.isPluginReady()) { 97 + var api = defer.getApi(); 98 + if (api) { 99 + var factory = api.getResolverFactory(); 100 + if (factory) { 101 + resolver = factory.createResolver("explorer"); 102 + } 103 + } 104 + } 105 + } 106 + 107 + var allFiles = app.vault.getMarkdownFiles(); 108 + var lowerText = text.toLowerCase(); 109 + for (var i = 0; i < allFiles.length; i++) { 110 + var resolved = null; 111 + if (resolver) { 112 + resolved = resolver.resolve(allFiles[i].path); 113 + } 114 + if (!resolved) { 115 + var cache = app.metadataCache.getFileCache(allFiles[i]); 116 + resolved = cache && cache.frontmatter && cache.frontmatter.title 117 + ? String(cache.frontmatter.title) 118 + : null; 119 + } 120 + if (resolved && resolved.toLowerCase() === lowerText) { 121 + foundFile = allFiles[i]; 122 + break; 123 + } 124 + } 125 + } 126 + 127 + if (foundFile) { 128 + var linkStr = foundFile.basename === text 129 + ? "[[" + text + "]]" 130 + : "[[" + foundFile.basename + "|" + safeAlias + "]]"; 131 + editor.replaceRange(linkStr, rangeFrom, rangeTo); 132 + await app.workspace.openLinkText(foundFile.path, sourcePath, "tab"); 133 + return; 134 + } 135 + 136 + // --- Create a new note with zk-prefixer style ID --- 137 + var now = new Date(); 138 + var pad = function(n, w) { return String(n).padStart(w, "0"); }; 139 + var yy = pad(now.getFullYear() % 100, 2); 140 + var mm = pad(now.getMonth() + 1, 2); 141 + var dd = pad(now.getDate(), 2); 142 + var hh = pad(now.getHours(), 2); 143 + var mi = pad(now.getMinutes(), 2); 144 + var zkId = yy + mm + dd + "-" + hh + mi; 145 + var fileName = zkId + ".md"; 146 + 147 + // Handle collision: if file with same zkId exists, append a letter suffix 148 + var suffix = ""; 149 + var alphabet = "abcdefghijklmnopqrstuvwxyz"; 150 + while (app.vault.getAbstractFileByPath(fileName)) { 151 + if (suffix === "") { 152 + suffix = "a"; 153 + } else { 154 + var idx = alphabet.indexOf(suffix); 155 + if (idx >= alphabet.length - 1) { 156 + throw new Error("Too many notes created this minute (" + zkId + "a-z exhausted)"); 157 + } 158 + suffix = alphabet[idx + 1]; 159 + } 160 + fileName = zkId + suffix + ".md"; 161 + } 162 + var noteId = suffix ? zkId + suffix : zkId; 163 + 164 + // Sanitize title for YAML: backslashes first, then double quotes, then newlines 165 + var safeTitle = text 166 + .replace(/\\/g, "\\\\") 167 + .replace(/"/g, '\\"') 168 + .replace(/[\r\n]+/g, " "); 169 + 170 + var frontmatter = [ 171 + "---", 172 + 'title: "' + safeTitle + '"', 173 + "tags:", 174 + 'date: "' + zkId + '"', 175 + "update:", 176 + "---", 177 + "", 178 + ].join("\n"); 179 + 180 + // Do editor replacement BEFORE async vault operation to avoid stale positions. 181 + // Save original text for rollback if vault.create fails. 182 + var originalText = editor.getRange(rangeFrom, rangeTo); 183 + var linkStr = "[[" + noteId + "|" + safeAlias + "]]"; 184 + editor.replaceRange(linkStr, rangeFrom, rangeTo); 185 + 186 + try { 187 + await app.vault.create(fileName, frontmatter); 188 + } catch(createErr) { 189 + // Rollback: restore original text since the note wasn't created 190 + var linkEnd = { 191 + line: rangeFrom.line, 192 + ch: rangeFrom.ch + linkStr.length 193 + }; 194 + editor.replaceRange(originalText, rangeFrom, linkEnd); 195 + throw createErr; 196 + } 197 + 198 + await app.workspace.openLinkText(noteId, sourcePath, "tab"); 199 + 200 + } catch(e) { 201 + console.error("followOrCreate error:", e); 202 + try { new Notice("followOrCreate: " + e.message, 5000); } catch(_) {} 203 + } 204 + })();
+105
home/profiles/obsidian/obsidian.vimrc
··· 1 + " ============================================================ 2 + " Obsidian Vimrc (requires Vimrc Support plugin by esm7) 3 + " ============================================================ 4 + 5 + " --- Clipboard --- 6 + " All yanks go to system clipboard 7 + set clipboard=unnamedplus 8 + 9 + " Make Y yank to end of line (consistent with C and D) 10 + nnoremap Y y$ 11 + 12 + " --- Unbind Space for use as leader --- 13 + unmap <Space> 14 + 15 + " --- Sidebar & Navigation --- 16 + " Toggle left sidebar 17 + exmap toggleSidebar obcommand app:toggle-left-sidebar 18 + nmap <Space>e :toggleSidebar<CR> 19 + 20 + " Reveal active file in file explorer 21 + exmap revealFile obcommand file-explorer:reveal-active-file 22 + nmap <Space>f :revealFile<CR> 23 + 24 + " Command palette 25 + exmap commandPalette obcommand command-palette:open 26 + nmap <Space>p :commandPalette<CR> 27 + 28 + " --- Tab Navigation --- 29 + " Next tab (matches Vim gt) 30 + exmap nextTab obcommand workspace:next-tab 31 + nmap gt :nextTab<CR> 32 + 33 + " Previous tab (matches Vim gT) 34 + exmap prevTab obcommand workspace:previous-tab 35 + nmap gT :prevTab<CR> 36 + 37 + " Close current tab 38 + exmap closeTab obcommand workspace:close 39 + nmap gd :closeTab<CR> 40 + 41 + " Jump past frontmatter and enter insert mode 42 + exmap goBody jscommand { var lines = editor.getValue().split("\n"); var count = 0; for (var i = 0; i < lines.length; i++) { if (lines[i].trim() === "---") { count++; if (count === 2) { editor.setCursor(i + 1, 0); return; } } } editor.setCursor(0, 0); } 43 + nmap gi :goBody<CR>i 44 + 45 + " --- Notes --- 46 + " Daily note (Periodic Notes) 47 + exmap dailyNote obcommand periodic-notes:open-daily-note 48 + nmap <Space>d :dailyNote<CR> 49 + 50 + " Weekly note (Periodic Notes) 51 + exmap weeklyNote obcommand periodic-notes:open-weekly-note 52 + nmap <Space>w :weeklyNote<CR> 53 + 54 + " New Zettelkasten note 55 + exmap newZK obcommand zk-prefixer 56 + nmap <Space>zn :newZK<CR> 57 + 58 + " Split (QuickAdd macro) 59 + exmap splitNote obcommand quickadd:choice:1d350c80-9184-4f8a-b37e-4e7c0712a19f 60 + nmap <Space>zs :splitNote<CR> 61 + 62 + " --- Folding --- 63 + exmap unfoldAtCursor obcommand editor:unfold 64 + exmap foldAtCursor obcommand editor:fold 65 + exmap toggleFold obcommand editor:toggle-fold 66 + exmap unfoldAll obcommand editor:unfold-all 67 + exmap foldAll obcommand editor:fold-all 68 + 69 + nmap zo :unfoldAtCursor<CR> 70 + nmap zc :foldAtCursor<CR> 71 + nmap za :toggleFold<CR> 72 + nmap zR :unfoldAll<CR> 73 + nmap zM :foldAll<CR> 74 + 75 + " --- Spelling --- 76 + " Open spelling suggestions via the editor suggest/context menu 77 + exmap spellcheck jscommand { var pos = view.editor.cm.coordsAtPos(view.editor.cm.state.selection.main.head); view.editor.cm.dom.dispatchEvent(new MouseEvent("contextmenu", {bubbles: true, cancelable: true, clientX: pos.left, clientY: pos.top})); } 78 + nmap z= :spellcheck<CR> 79 + 80 + " --- Quick Switcher --- 81 + exmap quickSwitcher obcommand switcher:open 82 + nmap <Space><Space> :quickSwitcher<CR> 83 + 84 + " --- Search --- 85 + exmap globalSearch obcommand global-search:open 86 + nmap <Space>fg :globalSearch<CR> 87 + 88 + " --- Surround (vim-surround style) --- 89 + " The plugin's built-in surroundOperator acts as a Vim operator: 90 + " ys{motion}{char} in normal mode (e.g. ysiw" to surround word with quotes) 91 + " S{char} in visual mode (e.g. viwS( to surround selection with parens) 92 + " It opens a prompt for the surround character; brackets auto-match. 93 + nunmap s 94 + vunmap s 95 + nmap ys <A-y>s 96 + vmap S <A-y>s 97 + 98 + " --- Follow or Create Note --- 99 + " Enter in normal/visual mode: if on a [[wikilink]], open it in a new tab. 100 + " Otherwise, check if a note with the word/selection as title exists: 101 + " - If it does, wrap in [[link]] and open in a new tab. 102 + " - If not, create a new zk-prefixed note with that title and link to it. 103 + exmap followOrCreate jsfile scripts/follow-or-create.js 104 + nmap <CR> :followOrCreate<CR> 105 + vmap <CR> :followOrCreate<CR>
+63 -25
home/profiles/opencode/default.nix
··· 18 18 # github-mcp-server binary path from nixpkgs 19 19 githubMcpServer = "${pkgs.github-mcp-server}/bin/github-mcp-server"; 20 20 21 + <<<<<<< HEAD 21 22 # Plugin sources fetched via flake inputs 22 23 handoffSrc = inputs.opencode-handoff; 24 + ======= 25 + # Upstream plugin/skill sources pinned via fetchFromGitHub 26 + opencode-handoff-src = pkgs.fetchFromGitHub { 27 + owner = "Chickensoupwithrice"; 28 + repo = "opencode-handoff"; 29 + rev = "b18d546e567c8c15c7ce8377f82f1b81cd838890"; 30 + hash = "sha256-1wDwwz7gcKLeCr0kqwQtQi7UWf12AYiPDL8YT9iFO08="; 31 + }; 32 + 33 + learning-opportunities-src = pkgs.fetchFromGitHub { 34 + owner = "DrCatHicks"; 35 + repo = "learning-opportunities"; 36 + rev = "e5f985d376461993253d285096ed0f4b4a095858"; 37 + hash = "sha256-xMpy9XxMaNCIAOr2dffrc5dyRt56jlam+XQjrNapsEw="; 38 + }; 39 + 40 + learning-goal-src = pkgs.fetchFromGitHub { 41 + owner = "DrCatHicks"; 42 + repo = "learning-goal"; 43 + rev = "cc7e3a6c7f0917501f1fd422bad81ab6f5040050"; 44 + hash = "sha256-lvQdDRuC9H+8F4Fud753c6NogVdZtGqgtjKbiSVZCig="; 45 + }; 46 + 47 + # Combined plugins derivation: assembles all local and upstream plugins 48 + # into a single directory with the layout OpenCode expects. 49 + # 50 + # OpenCode globs plugins/*.{ts,js} and calls every export as a plugin 51 + # initializer, so only actual entry points belong at the top level. 52 + # Helper modules (tools.ts, files.ts, etc.) go in subdirectories to 53 + # avoid being auto-loaded. 54 + opencode-plugins = pkgs.runCommand "opencode-plugins" { } '' 55 + mkdir -p $out/handoff-src 56 + 57 + # opencode-handoff: thin entry point re-exports from a subdirectory 58 + # so OpenCode only auto-loads the plugin, not its helper modules 59 + cat > $out/handoff.ts <<'ENTRY' 60 + export { HandoffPlugin } from "./handoff-src/plugin" 61 + ENTRY 62 + ln -s ${opencode-handoff-src}/src/* $out/handoff-src/ 63 + 64 + # learning-opportunities-auto: OpenCode port of the upstream 65 + # Claude Code PostToolUse hook (local file, not a patch of upstream) 66 + cp ${./plugins/learning-opportunities-auto.js} $out/learning-opportunities-auto.js 67 + ''; 68 + >>>>>>> 64f30fc4d82e01dc9b381096bb4cc8cd5498c6c4 23 69 in 24 70 { 25 71 home.packages = [ ··· 68 114 "opencode/themes".source = ./themes; 69 115 "opencode/agents".source = ./agents; 70 116 "opencode/commands".source = ./commands; 117 + <<<<<<< HEAD 71 118 "opencode/skills".source = ./skills; 119 + ======= 120 + 121 + # Local skills 122 + "opencode/skills/tmux".source = ./skills/tmux; 123 + "opencode/skills/session-search.disabled".source = ./skills/session-search.disabled; 124 + 125 + # Upstream skills via fetchFromGitHub 126 + "opencode/skills/learning-opportunities".source = 127 + "${learning-opportunities-src}/learning-opportunities/skills/learning-opportunities"; 128 + "opencode/skills/learning-goal".source = "${learning-goal-src}/learning-goal/skills/learning-goal"; 129 + "opencode/skills/orient".source = "${learning-opportunities-src}/orient/skills/orient"; 130 + 131 + # Plugins: combined derivation with correct layout for OpenCode's 132 + # plugin loader (only entry points at top level) 133 + "opencode/plugins".source = opencode-plugins; 134 + >>>>>>> 64f30fc4d82e01dc9b381096bb4cc8cd5498c6c4 72 135 }; 73 136 74 137 home.file = lib.mkIf isBox { 75 138 "usr/.opencode/agents.md".source = ./agents/box.md; 76 139 }; 77 - 78 - # Copy plugin files into the writable plugins directory rather than symlinking. 79 - # Bun's import() resolves symlinks to their real path (inside the read-only Nix 80 - # store), then searches for node_modules/ relative to that real path -- which fails 81 - # because node_modules/ lives at ~/.config/opencode/node_modules/, not in the store. 82 - # Copying the file ensures it physically lives alongside node_modules/ so Bun's 83 - # module resolution can find dependencies if they're ever needed. 84 - home.activation.opencode-plugins = lib.hm.dag.entryAfter [ "writeBoundary" ] '' 85 - mkdir -p "$HOME/.config/opencode/plugins" 86 - mkdir -p "$HOME/.config/opencode/plugins/handoff" 87 - # Local plugins (top-level files are auto-loaded by OpenCode) 88 - install -m644 ${./plugins/learning-opportunities-auto.js} \ 89 - "$HOME/.config/opencode/plugins/learning-opportunities-auto.js" 90 - # opencode-handoff: copy entry point and patch imports to use subdir. 91 - # OpenCode globs plugins/*.{ts,js} so only handoff.ts is auto-loaded. 92 - ${pkgs.gnused}/bin/sed \ 93 - -e 's|from "./tools"|from "./handoff/tools"|' \ 94 - -e 's|from "./files"|from "./handoff/files"|' \ 95 - ${handoffSrc}/src/plugin.ts > "$HOME/.config/opencode/plugins/handoff.ts" 96 - cp -f ${handoffSrc}/src/tools.ts "$HOME/.config/opencode/plugins/handoff/tools.ts" 97 - cp -f ${handoffSrc}/src/files.ts "$HOME/.config/opencode/plugins/handoff/files.ts" 98 - cp -f ${handoffSrc}/src/vendor.ts "$HOME/.config/opencode/plugins/handoff/vendor.ts" 99 - chmod 644 "$HOME/.config/opencode/plugins/handoff.ts" \ 100 - "$HOME/.config/opencode/plugins/handoff"/*.ts 101 - ''; 102 140 }
+74 -13
hosts/profiles/gitea/default.nix
··· 1 - { self, config, lib, pkgs, ... }: 1 + { 2 + self, 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 2 8 3 9 { 4 10 age.secrets.gitea-dbpass.file = "${self}/secrets/gitea-dbpass.age"; ··· 55 61 isSystemUser = true; 56 62 }; 57 63 58 - users.groups.gitea = {}; 64 + users.groups.gitea = { }; 59 65 60 66 environment.systemPackages = [ pkgs.pandoc ]; 61 67 services.postgresql = { ··· 69 75 # gitea-users postgres gitea 70 76 # ''; 71 77 ensureDatabases = [ "gitea" ]; 72 - ensureUsers = [{ 73 - name = "gitea"; 74 - ensureDBOwnership = true; 75 - }]; 78 + ensureUsers = [ 79 + { 80 + name = "gitea"; 81 + ensureDBOwnership = true; 82 + } 83 + ]; 76 84 # TODO 77 - # initialScript 85 + # initialScript 78 86 # set password for gitea user 79 87 }; 80 88 89 + # Anubis proof-of-work bot protection in front of Forgejo 90 + services.anubis.instances."gitea" = { 91 + settings = { 92 + TARGET = "http://localhost:3001"; 93 + BIND = "/run/anubis/anubis-gitea/anubis.sock"; 94 + METRICS_BIND = "/run/anubis/anubis-gitea/metrics.sock"; 95 + }; 96 + botPolicy = { 97 + bots = [ 98 + # Allow git CLI clients through without challenge 99 + { 100 + name = "git-client"; 101 + user_agent_regex = "^git/"; 102 + action = "ALLOW"; 103 + } 104 + # Allow Go module fetches (go get) 105 + { 106 + name = "go-http-client"; 107 + user_agent_regex = "^Go-http-client"; 108 + action = "ALLOW"; 109 + } 110 + # Allow well-known paths and static assets 111 + { 112 + name = "well-known"; 113 + path_regex = "^/.well-known/.*$"; 114 + action = "ALLOW"; 115 + } 116 + { 117 + name = "favicon"; 118 + path_regex = "^/favicon\\.ico$"; 119 + action = "ALLOW"; 120 + } 121 + { 122 + name = "robots-txt"; 123 + path_regex = "^/robots\\.txt$"; 124 + action = "ALLOW"; 125 + } 126 + # Challenge browser-like user agents (scrapers, bots pretending to be browsers) 127 + { 128 + name = "generic-browser"; 129 + user_agent_regex = "Mozilla"; 130 + action = "CHALLENGE"; 131 + } 132 + ]; 133 + }; 134 + }; 135 + 136 + # nginx needs access to the Anubis unix socket 137 + users.users.nginx.extraGroups = [ config.users.groups.anubis.name ]; 138 + 81 139 services.nginx = { 82 - enable = true; # Enable Nginx 140 + enable = true; 83 141 recommendedGzipSettings = true; 84 142 recommendedOptimisation = true; 85 143 recommendedProxySettings = true; 86 144 recommendedTlsSettings = true; 87 145 virtualHosts."git.sealight.xyz" = { 88 - # Gitea hostname 89 - enableACME = true; # Use ACME certs 90 - forceSSL = true; # Force SSL 91 - locations."/".proxyPass = "http://localhost:3001/"; # Proxy Gitea 146 + enableACME = true; 147 + forceSSL = true; 148 + locations."/".proxyPass = "http://unix:${config.services.anubis.instances.gitea.settings.BIND}"; 92 149 }; 93 150 }; 94 - networking.firewall.allowedTCPPorts = [ 80 443 22 ]; 151 + networking.firewall.allowedTCPPorts = [ 152 + 80 153 + 443 154 + 22 155 + ]; 95 156 }
+64 -11
hosts/profiles/sync/tv/get-tv.sh
··· 8 8 TRACKING_FILE="/tank/media/tv/.downloaded_shows" 9 9 LOG_FILE="/tank/media/tv/download-log" 10 10 LOCK_FILE="/tmp/get-tv-sync.lock" 11 - SONARR_API_KEY="PLACEHOLDER" 11 + SONARR_API_KEY="9dba6d5f31eb42cf8c65f4f6f21cc8d2" 12 12 SONARR_URL="http://localhost:8989" 13 13 14 14 umask 002 ··· 18 18 touch "$TRACKING_FILE" 19 19 20 20 log() { echo "$(date): $1" >>"$LOG_FILE"; } 21 + 22 + # Extract a match key from a release name for fuzzy matching. 23 + # Sonarr's sourceTitle often differs from the actual filename on the seedbox: 24 + # - sourceTitle omits file extensions (.mkv, .ts, etc.) 25 + # - sourceTitle omits episode titles that appear in the actual filename 26 + # We match on episode identifier + release group, which are consistent on both sides. 27 + # Returns "episode_id|||group" e.g. "Top.Chef.S23E01|||RAWR" or "Jeopardy.2026.03.19|||BTN" 28 + match_key() { 29 + local name="$1" 30 + # Strip known video file extensions 31 + name="${name%.mkv}" 32 + name="${name%.ts}" 33 + name="${name%.mp4}" 34 + name="${name%.avi}" 35 + name="${name%.m4v}" 36 + 37 + local ep_id="" group="" 38 + 39 + # Extract release group: everything after the last hyphen 40 + if [[ "$name" == *-* ]]; then 41 + group="${name##*-}" 42 + fi 43 + 44 + # Extract episode identifier: show name + S##E##, YYYY.MM.DD, or S## pattern 45 + if [[ "$name" =~ ^(.*\.[Ss][0-9]+[Ee][0-9]+) ]]; then 46 + ep_id="${BASH_REMATCH[1]}" 47 + elif [[ "$name" =~ ^(.*\.[0-9]{4}\.[0-9]{2}\.[0-9]{2}) ]]; then 48 + ep_id="${BASH_REMATCH[1]}" 49 + elif [[ "$name" =~ ^(.*\.[Ss][0-9]+)\. ]]; then 50 + ep_id="${BASH_REMATCH[1]}" 51 + fi 52 + 53 + if [ -n "$ep_id" ] && [ -n "$group" ]; then 54 + echo "${ep_id}|||${group}" 55 + fi 56 + } 21 57 22 58 # Acquire exclusive lock to prevent concurrent runs 23 59 exec 9>"$LOCK_FILE" ··· 136 172 continue 137 173 fi 138 174 139 - # Check if it exists on the remote seedbox (exact match on entry name) 140 - # Use ENVIRON to pass source_title to awk, avoiding backslash interpretation from -v 141 - REMOTE_MATCH=$(SOURCE_TITLE="$source_title" awk -F'\t' 'BEGIN { name = ENVIRON["SOURCE_TITLE"] } $2 == name { print; exit }' "$REMOTE_LISTING_FILE") || true 175 + # Match sourceTitle against remote seedbox entries using episode ID + release group. 176 + # Sonarr's sourceTitle often differs from the actual filename (missing extension, 177 + # missing episode title in the name), so exact matching doesn't work. 178 + SOURCE_KEY=$(match_key "$source_title") 179 + if [ -z "$SOURCE_KEY" ]; then 180 + log "WARNING: Could not extract match key from sourceTitle: $source_title" 181 + continue 182 + fi 183 + 184 + REMOTE_MATCH="" 185 + while IFS=$'\t' read -r rtype rname; do 186 + [ -z "$rname" ] && continue 187 + REMOTE_KEY=$(match_key "$rname") 188 + if [ "$SOURCE_KEY" = "$REMOTE_KEY" ]; then 189 + REMOTE_MATCH="$rtype"$'\t'"$rname" 190 + break 191 + fi 192 + done <"$REMOTE_LISTING_FILE" 193 + 142 194 if [ -z "$REMOTE_MATCH" ]; then 143 195 log "Not yet on seedbox (still downloading?): $source_title" 144 196 continue ··· 146 198 147 199 # Determine if it's a directory or file from the listing 148 200 ENTRY_TYPE=$(echo "$REMOTE_MATCH" | cut -f1) 201 + REMOTE_NAME=$(echo "$REMOTE_MATCH" | cut -f2) 149 202 150 - log "Downloading '$source_title' -> '$series_folder/'" 203 + log "Downloading '$source_title' (remote: '$REMOTE_NAME') -> '$series_folder/'" 151 204 mkdir -p "$LOCAL_PATH/$series_folder" 152 205 153 206 if [ "$ENTRY_TYPE" = "d" ]; then 154 207 # Directory: rsync recursively, trailing slash to put contents into series folder 155 208 if rsync -rs --partial --timeout=600 --log-file="$LOG_FILE" \ 156 - "$REMOTE_HOST:$REMOTE_PATH$source_title/" \ 209 + "$REMOTE_HOST:$REMOTE_PATH$REMOTE_NAME/" \ 157 210 "$LOCAL_PATH/$series_folder/"; then 158 211 echo "$source_title" >>"$TRACKING_FILE" 159 - log "Successfully downloaded directory: $source_title" 212 + log "Successfully downloaded directory: $REMOTE_NAME" 160 213 DOWNLOADED=$((DOWNLOADED + 1)) 161 214 else 162 - log "Failed to download directory: $source_title" 215 + log "Failed to download directory: $REMOTE_NAME" 163 216 fi 164 217 else 165 218 # Single file: rsync into series folder 166 219 if rsync -s --partial --timeout=600 --log-file="$LOG_FILE" \ 167 - "$REMOTE_HOST:$REMOTE_PATH$source_title" \ 220 + "$REMOTE_HOST:$REMOTE_PATH$REMOTE_NAME" \ 168 221 "$LOCAL_PATH/$series_folder/"; then 169 222 echo "$source_title" >>"$TRACKING_FILE" 170 - log "Successfully downloaded file: $source_title" 223 + log "Successfully downloaded file: $REMOTE_NAME" 171 224 DOWNLOADED=$((DOWNLOADED + 1)) 172 225 else 173 - log "Failed to download file: $source_title" 226 + log "Failed to download file: $REMOTE_NAME" 174 227 fi 175 228 fi 176 229 done <<<"$RECORDS"
+16
pkgs/obsidian-plugins/default.nix
··· 163 163 meta.description = "Auto-load a startup file with Obsidian Vim commands"; 164 164 }; 165 165 166 + obsidian-front-matter-title-plugin = buildObsidianPlugin { 167 + pname = "obsidian-front-matter-title-plugin"; 168 + version = "3.13.1"; 169 + owner = "Snezhig"; 170 + repo = "obsidian-front-matter-title"; 171 + mainJs = fetchurl { 172 + url = "https://github.com/Snezhig/obsidian-front-matter-title/releases/download/3.13.1/main.js"; 173 + sha256 = "0azbj27xyx8g3n7iyc481ndp9q1nqh6g7p34ngg892xc880a9gkn"; 174 + }; 175 + manifestJson = fetchurl { 176 + url = "https://github.com/Snezhig/obsidian-front-matter-title/releases/download/3.13.1/manifest.json"; 177 + sha256 = "0kbs75d0xs19cmhs3kpmd48wk5832z1krdlw1hv998ararj9ww6q"; 178 + }; 179 + meta.description = "Display frontmatter title instead of filename in explorer, graph, etc."; 180 + }; 181 + 166 182 obsidian-atmosphere = buildObsidianPlugin { 167 183 pname = "obsidian-atmosphere"; 168 184 version = "0.1.19";