This repository has no description
0

Configure Feed

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

skills.hedgedoc: add hedgedoc pad skill

+331
+83
skills/hedgedoc/SKILL.md
··· 1 + --- 2 + name: hedgedoc 3 + description: Manage HedgeDoc pads on pad.lassul.us — create, read, edit, and get info on collaborative markdown documents. Use when the user wants to work with pads, notes, or shared documents. 4 + --- 5 + 6 + # HedgeDoc Pad Skill 7 + 8 + Manage HedgeDoc pads on `pad.lassul.us` — create, read, and edit collaborative markdown documents. 9 + 10 + ## Instance 11 + 12 + - **URL**: `https://pad.lassul.us` 13 + - **Access**: Anonymous (no auth required) 14 + 15 + ## Script 16 + 17 + Use `./pad.sh <command>` (scripts are next to this SKILL.md): 18 + 19 + ### Read a pad 20 + ```bash 21 + ./pad.sh read <pad-id> 22 + ``` 23 + 24 + ### Create a new pad 25 + ```bash 26 + # From stdin 27 + echo "# My Document" | ./pad.sh create 28 + 29 + # From a file 30 + ./pad.sh create /path/to/file.md 31 + ``` 32 + Returns the new pad URL. 33 + 34 + ### Edit a pad (in-place) 35 + ```bash 36 + # From stdin 37 + echo "# Updated content" | ./pad.sh edit <pad-id> 38 + 39 + # From a file 40 + ./pad.sh edit <pad-id> /path/to/file.md 41 + ``` 42 + Edits the pad in-place via the HedgeDoc websocket API (socket.io + OT). No new pad created. 43 + 44 + ### Get pad info 45 + ```bash 46 + ./pad.sh info <pad-id> 47 + ``` 48 + Returns JSON with title, description, viewcount, timestamps. 49 + 50 + ## Typical Workflow 51 + 52 + 1. **Read** the pad: `pad.sh read <id>` → get current markdown 53 + 2. **Edit** locally: save modified content to a temp file 54 + 3. **Write back**: `pad.sh edit <id> /tmp/updated.md` 55 + 4. **Share**: `https://pad.lassul.us/<id>` 56 + 57 + ## How it works 58 + 59 + The edit command connects to HedgeDoc's socket.io websocket, receives the current document state and revision number, then sends an OT (operational transform) operation that deletes all existing content and inserts the new content. The server acknowledges with an `ack` event. 60 + 61 + ## Mermaid Diagrams 62 + 63 + HedgeDoc renders Mermaid diagrams natively. Use fenced code blocks: 64 + 65 + ````markdown 66 + ```mermaid 67 + graph LR 68 + A --> B --> C 69 + ``` 70 + ```` 71 + 72 + Prefer Mermaid over ASCII art for architecture diagrams, flowcharts, etc. in pads. 73 + 74 + **Always quote node labels with special characters** — labels containing `/`, `~`, `*`, `+`, `.`, `:`, `(`, `)` must use double quotes: `Node["~/Mail/new/"]` not `Node[~/Mail/new/]`. Unquoted special chars break Mermaid rendering. 75 + 76 + ## Notes 77 + 78 + - `allowFreeURL = true` — pads can have custom slugs 79 + - `defaultPermission = "freely"` — all pads are world-editable via URL 80 + - Edit replaces the entire pad content (full replacement, not partial edit) 81 + - Active browser sessions will see the change in real-time (pushed via socket.io) 82 + - Pad IDs are from the URL: `https://pad.lassul.us/{PADID}` 83 + - Edit uses UTF-16 code unit counting (JavaScript string length) for OT operations
+47
skills/hedgedoc/default.nix
··· 1 + { 2 + python, 3 + makeWrapper, 4 + curl, 5 + lib, 6 + stdenv, 7 + ... 8 + }: 9 + 10 + let 11 + pythonWithDeps = python.withPackages (ps: [ ps.websocket-client ]); 12 + in 13 + stdenv.mkDerivation { 14 + pname = "hedgedoc-skill"; 15 + version = "0.1.0"; 16 + src = ./.; 17 + 18 + nativeBuildInputs = [ makeWrapper ]; 19 + 20 + buildInputs = [ 21 + curl 22 + pythonWithDeps 23 + ]; 24 + 25 + installPhase = '' 26 + mkdir -p $out/bin $out/share/hedgedoc 27 + cp pad-edit.py $out/share/hedgedoc/pad-edit.py 28 + chmod +x $out/share/hedgedoc/pad-edit.py 29 + cp pad.sh $out/share/hedgedoc/pad.sh 30 + chmod +x $out/share/hedgedoc/pad.sh 31 + cp SKILL.md $out/share/hedgedoc/SKILL.md 32 + 33 + wrapProgram $out/share/hedgedoc/pad-edit.py \ 34 + --prefix PATH : ${lib.makeBinPath [ pythonWithDeps ]} 35 + 36 + wrapProgram $out/share/hedgedoc/pad.sh \ 37 + --prefix PATH : ${lib.makeBinPath [ curl ]} \ 38 + --set SKILL_DIR $out/share/hedgedoc 39 + 40 + ln -s $out/share/hedgedoc/pad.sh $out/bin/pad.sh 41 + ''; 42 + 43 + meta = { 44 + description = "Manage HedgeDoc pads - create, read, edit collaborative markdown documents"; 45 + mainProgram = "pad.sh"; 46 + }; 47 + }
+138
skills/hedgedoc/pad-edit.py
··· 1 + #!/usr/bin/env python3 2 + """Edit a HedgeDoc pad in-place via websocket (socket.io protocol).""" 3 + import sys 4 + import json 5 + import http.cookiejar 6 + import urllib.request 7 + import ssl 8 + 9 + try: 10 + import websocket 11 + except ImportError: 12 + import subprocess 13 + import os 14 + 15 + # Bootstrap: re-exec under nix shell with websocket-client 16 + os.execvp( 17 + "nix", 18 + [ 19 + "nix", 20 + "shell", 21 + "nixpkgs#python3Packages.websocket-client", 22 + "-c", 23 + "python3", 24 + __file__, 25 + ] 26 + + sys.argv[1:], 27 + ) 28 + 29 + HEDGEDOC_URL = "https://pad.lassul.us" 30 + 31 + 32 + def js_length(s): 33 + """Return JavaScript string length (UTF-16 code units, not Unicode codepoints).""" 34 + return len(s.encode("utf-16-le")) // 2 35 + 36 + 37 + def main(): 38 + if len(sys.argv) < 2: 39 + print("Usage: pad-edit.py <pad-id> [file]", file=sys.stderr) 40 + print(" echo 'content' | pad-edit.py <pad-id>", file=sys.stderr) 41 + sys.exit(1) 42 + 43 + pad_id = sys.argv[1] 44 + 45 + # Read new content 46 + if len(sys.argv) >= 3 and sys.argv[2] != "-": 47 + with open(sys.argv[2]) as f: 48 + new_content = f.read() 49 + else: 50 + new_content = sys.stdin.read() 51 + 52 + # Strip single trailing newline (added by shell heredoc/echo) 53 + if new_content.endswith("\n"): 54 + new_content = new_content[:-1] 55 + 56 + # Step 1: Get session cookie 57 + ctx = ssl.create_default_context() 58 + cj = http.cookiejar.CookieJar() 59 + opener = urllib.request.build_opener( 60 + urllib.request.HTTPCookieProcessor(cj), 61 + urllib.request.HTTPSHandler(context=ctx), 62 + ) 63 + opener.open(f"{HEDGEDOC_URL}/{pad_id}") 64 + cookie_str = "; ".join(f"{c.name}={c.value}" for c in cj) 65 + if not cookie_str: 66 + print("ERROR: Failed to get session cookie", file=sys.stderr) 67 + sys.exit(1) 68 + 69 + # Step 2: Connect websocket 70 + ws_url = f"wss://pad.lassul.us/socket.io/?noteId={pad_id}&EIO=4&transport=websocket" 71 + ws = websocket.create_connection( 72 + ws_url, 73 + header=[f"Cookie: {cookie_str}", f"Origin: {HEDGEDOC_URL}"], 74 + sslopt={"cert_reqs": ssl.CERT_REQUIRED}, 75 + ) 76 + 77 + ws.recv() # handshake: 0{"sid":...} 78 + ws.send("40") # namespace connect 79 + 80 + # Read until doc event, handle pings 81 + revision = None 82 + old_js_len = None 83 + for _ in range(20): 84 + ws.settimeout(5) 85 + msg = ws.recv() 86 + if msg == "2": 87 + ws.send("3") 88 + continue 89 + if '"doc"' in msg: 90 + payload = json.loads(msg[2:]) 91 + doc_data = payload[1] 92 + revision = doc_data["revision"] 93 + old_js_len = js_length(doc_data["str"]) 94 + break 95 + 96 + if revision is None: 97 + print("ERROR: Did not receive document state", file=sys.stderr) 98 + ws.close() 99 + sys.exit(1) 100 + 101 + # Step 3: Build and send OT operation 102 + escaped_new = json.dumps(new_content, ensure_ascii=False) 103 + 104 + if old_js_len == 0: 105 + op = f"[{escaped_new}]" 106 + else: 107 + op = f"[-{old_js_len},{escaped_new}]" 108 + 109 + ws.send(f'42["operation",{revision},{op}]') 110 + 111 + # Step 4: Wait for ack 112 + ack_received = False 113 + for _ in range(20): 114 + try: 115 + ws.settimeout(5) 116 + msg = ws.recv() 117 + if msg == "2": 118 + ws.send("3") 119 + continue 120 + if '"ack"' in msg: 121 + ack_received = True 122 + break 123 + except websocket.WebSocketTimeoutException: 124 + break 125 + 126 + ws.close() 127 + 128 + if ack_received: 129 + print(f"✅ Updated {HEDGEDOC_URL}/{pad_id}") 130 + else: 131 + print( 132 + f"⚠️ No ack received. Check {HEDGEDOC_URL}/{pad_id}", file=sys.stderr 133 + ) 134 + sys.exit(1) 135 + 136 + 137 + if __name__ == "__main__": 138 + main()
+58
skills/hedgedoc/pad.sh
··· 1 + #!/usr/bin/env bash 2 + # HedgeDoc pad management script 3 + # Usage: 4 + # pad.sh read <pad-id> - Read pad content 5 + # pad.sh create [file|-] - Create new pad, returns URL 6 + # pad.sh edit <pad-id> [file|-] - Replace pad content in-place 7 + # pad.sh info <pad-id> - Get pad metadata 8 + 9 + set -euo pipefail 10 + 11 + HEDGEDOC_URL="https://pad.lassul.us" 12 + SKILL_DIR="${SKILL_DIR:-$(cd "$(dirname "$0")" && pwd)}" 13 + 14 + cmd="${1:-help}" 15 + shift || true 16 + 17 + case "$cmd" in 18 + read) 19 + PADID="${1:?Usage: pad.sh read <pad-id>}" 20 + curl -sf "${HEDGEDOC_URL}/${PADID}/download" 21 + ;; 22 + 23 + create) 24 + if [ -n "${1:-}" ] && [ -f "$1" ]; then 25 + CONTENT=$(cat "$1") 26 + elif [ -n "${1:-}" ] && [ "$1" = "-" ]; then 27 + CONTENT=$(cat) 28 + else 29 + CONTENT=$(cat) 30 + fi 31 + PAD_URL=$(curl -s -o /dev/null -w "%{redirect_url}" \ 32 + -X POST "${HEDGEDOC_URL}/new" \ 33 + -H "Content-Type: text/markdown" \ 34 + -d "$CONTENT") 35 + echo "$PAD_URL" 36 + ;; 37 + 38 + edit) 39 + PADID="${1:?Usage: pad.sh edit <pad-id> [file|-]}" 40 + shift 41 + exec "$SKILL_DIR/pad-edit.py" "$PADID" "$@" 42 + ;; 43 + 44 + info) 45 + PADID="${1:?Usage: pad.sh info <pad-id>}" 46 + curl -sf "${HEDGEDOC_URL}/${PADID}/info" 47 + ;; 48 + 49 + help|*) 50 + echo "Usage: pad.sh <command> [args]" 51 + echo "" 52 + echo "Commands:" 53 + echo " read <pad-id> Read pad content (markdown)" 54 + echo " create [file|-] Create new pad, returns URL" 55 + echo " edit <pad-id> [file|-] Replace pad content in-place via websocket" 56 + echo " info <pad-id> Get pad metadata (JSON)" 57 + ;; 58 + esac
+5
tools/pi/flake-module.nix
··· 62 62 ${pkgs.gnused}/bin/sed -i 's/"ctrl+u"/"ctrl+shift+u"/' $out/lib/node_modules/shitty-extensions/extensions/ultrathink.ts 63 63 ${pkgs.gnused}/bin/sed -i 's/"ctrl+r"/"ctrl+shift+r"/' $out/lib/node_modules/shitty-extensions/extensions/speedreading.ts 64 64 65 + # Add hedgedoc skill 66 + ln -s ${ 67 + self.legacyPackages.${pkgs.system}.skills.hedgedoc 68 + }/share/hedgedoc $out/lib/node_modules/shitty-extensions/skills/hedgedoc 69 + 65 70 # Patch permission extension to use pw-play for peon sounds on Linux 66 71 ${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux '' 67 72 ${pkgs.python3}/bin/python3 ${./patch-permission-sound.py} \