this repo has no description
2
fork

Configure Feed

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

obsidian

+356 -76
-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>
+22
home/profiles/opencode/default.nix
··· 17 17 18 18 # github-mcp-server binary path from nixpkgs 19 19 githubMcpServer = "${pkgs.github-mcp-server}/bin/github-mcp-server"; 20 + 21 + # opencode-handoff plugin: fetch source and assemble into a single directory 22 + # so the relative import from the entry point resolves correctly 23 + opencode-handoff-src = pkgs.fetchFromGitHub { 24 + owner = "Chickensoupwithrice"; 25 + repo = "opencode-handoff"; 26 + rev = "e66697d"; 27 + hash = "sha256-/drpkGLxKmoYlo3MZqYnQSedwLWHl7TQuO+NkY21xuQ="; 28 + }; 29 + 30 + opencode-handoff-plugin = pkgs.runCommand "opencode-handoff-plugin" { } '' 31 + mkdir -p $out 32 + cat > $out/handoff.ts <<'ENTRY' 33 + export { HandoffPlugin } from "./handoff-src/plugin" 34 + ENTRY 35 + ln -s ${opencode-handoff-src}/src $out/handoff-src 36 + ''; 20 37 in 21 38 { 22 39 home.packages = [ ··· 66 83 "opencode/agents".source = ./agents; 67 84 "opencode/commands".source = ./commands; 68 85 "opencode/skills".source = ./skills; 86 + 87 + # opencode-handoff plugin: single derivation with entry point + source 88 + # so relative imports resolve correctly (home-manager would otherwise 89 + # place them in separate nix store paths) 90 + "opencode/plugins".source = opencode-handoff-plugin; 69 91 }; 70 92 71 93 home.file = lib.mkIf isBox {
+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";