Kieran's opinionated (and probably slightly dumb) nix config

chore: use flake

dunkirk.sh 6cb12136 18cbb1fc

verified
+443 -443
+443 -443
modules/home/system/shell.nix
··· 10 10 tangled = cfg.tangled; 11 11 12 12 tangled-setup = pkgs.writeShellScriptBin "tangled-setup" '' 13 - set -euo pipefail 13 + set -euo pipefail 14 14 15 - # Defaults (configured by Nix) 16 - PLC_ID="${tangled.plcId}" 17 - GITHUB_USER="${tangled.githubUser}" 18 - KNOT_HOST="${tangled.knotHost}" 19 - BRANCH="${tangled.defaultBranch}" 20 - FORCE=false 15 + # Defaults (configured by Nix) 16 + PLC_ID="${tangled.plcId}" 17 + GITHUB_USER="${tangled.githubUser}" 18 + KNOT_HOST="${tangled.knotHost}" 19 + BRANCH="${tangled.defaultBranch}" 20 + FORCE=false 21 21 22 - usage() { 23 - cat <<EOF 24 - Usage: tangled-setup [OPTIONS] 22 + usage() { 23 + cat <<EOF 24 + Usage: tangled-setup [OPTIONS] 25 25 26 - Configure git remotes for tangled workflow. 27 - Sets: origin → knot, github → GitHub 26 + Configure git remotes for tangled workflow. 27 + Sets: origin → knot, github → GitHub 28 28 29 - Options: 30 - --plc ID PLC ID (default: $PLC_ID) 31 - --github-user USER GitHub username (default: $GITHUB_USER) 32 - --knot HOST Knot host (default: $KNOT_HOST) 33 - --branch BRANCH Default branch (default: $BRANCH) 34 - -f, --force Overwrite existing remotes without checking 35 - -h, --help Show this help 36 - EOF 37 - exit 0 38 - } 29 + Options: 30 + --plc ID PLC ID (default: $PLC_ID) 31 + --github-user USER GitHub username (default: $GITHUB_USER) 32 + --knot HOST Knot host (default: $KNOT_HOST) 33 + --branch BRANCH Default branch (default: $BRANCH) 34 + -f, --force Overwrite existing remotes without checking 35 + -h, --help Show this help 36 + EOF 37 + exit 0 38 + } 39 39 40 - while [[ $# -gt 0 ]]; do 41 - case "$1" in 42 - -h|--help) usage ;; 43 - --plc) PLC_ID="$2"; shift 2 ;; 44 - --github-user) GITHUB_USER="$2"; shift 2 ;; 45 - --knot) KNOT_HOST="$2"; shift 2 ;; 46 - --branch) BRANCH="$2"; shift 2 ;; 47 - -f|--force) FORCE=true; shift ;; 48 - -*) echo "Unknown option: $1" >&2; exit 1 ;; 49 - *) shift ;; 50 - esac 51 - done 40 + while [[ $# -gt 0 ]]; do 41 + case "$1" in 42 + -h|--help) usage ;; 43 + --plc) PLC_ID="$2"; shift 2 ;; 44 + --github-user) GITHUB_USER="$2"; shift 2 ;; 45 + --knot) KNOT_HOST="$2"; shift 2 ;; 46 + --branch) BRANCH="$2"; shift 2 ;; 47 + -f|--force) FORCE=true; shift ;; 48 + -*) echo "Unknown option: $1" >&2; exit 1 ;; 49 + *) shift ;; 50 + esac 51 + done 52 52 53 - if ! ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 54 - echo "Error: Not a git repository" >&2 55 - exit 1 56 - fi 53 + if ! ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 54 + echo "Error: Not a git repository" >&2 55 + exit 1 56 + fi 57 57 58 - repo_name=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)") 59 - knot_url="git@$KNOT_HOST:$PLC_ID/$repo_name" 60 - github_url="git@github.com:$GITHUB_USER/$repo_name.git" 58 + repo_name=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)") 59 + knot_url="git@$KNOT_HOST:$PLC_ID/$repo_name" 60 + github_url="git@github.com:$GITHUB_USER/$repo_name.git" 61 61 62 - echo "Configuring remotes for: $repo_name" 62 + echo "Configuring remotes for: $repo_name" 63 63 64 - # Configure origin → knot 65 - current_origin=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || true) 66 - if [[ -z "$current_origin" ]]; then 67 - ${pkgs.git}/bin/git remote add origin "$knot_url" 68 - echo "✓ origin → $knot_url" 69 - elif [[ "$current_origin" == *"$KNOT_HOST"* ]]; then 70 - echo "✓ origin → $current_origin (already knot)" 71 - elif [[ "$FORCE" == true ]]; then 72 - ${pkgs.git}/bin/git remote set-url origin "$knot_url" 73 - echo "✓ origin → $knot_url (was: $current_origin)" 74 - else 75 - echo "! origin → $current_origin (use -f to override)" 76 - fi 64 + # Configure origin → knot 65 + current_origin=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || true) 66 + if [[ -z "$current_origin" ]]; then 67 + ${pkgs.git}/bin/git remote add origin "$knot_url" 68 + echo "✓ origin → $knot_url" 69 + elif [[ "$current_origin" == *"$KNOT_HOST"* ]]; then 70 + echo "✓ origin → $current_origin (already knot)" 71 + elif [[ "$FORCE" == true ]]; then 72 + ${pkgs.git}/bin/git remote set-url origin "$knot_url" 73 + echo "✓ origin → $knot_url (was: $current_origin)" 74 + else 75 + echo "! origin → $current_origin (use -f to override)" 76 + fi 77 77 78 - # Configure github remote 79 - current_github=$(${pkgs.git}/bin/git remote get-url github 2>/dev/null || true) 80 - if [[ -z "$current_github" ]]; then 81 - ${pkgs.git}/bin/git remote add github "$github_url" 82 - echo "✓ github → $github_url" 83 - elif [[ "$FORCE" == true ]]; then 84 - ${pkgs.git}/bin/git remote set-url github "$github_url" 85 - echo "✓ github → $github_url (was: $current_github)" 86 - else 87 - echo "✓ github → $current_github" 88 - fi 78 + # Configure github remote 79 + current_github=$(${pkgs.git}/bin/git remote get-url github 2>/dev/null || true) 80 + if [[ -z "$current_github" ]]; then 81 + ${pkgs.git}/bin/git remote add github "$github_url" 82 + echo "✓ github → $github_url" 83 + elif [[ "$FORCE" == true ]]; then 84 + ${pkgs.git}/bin/git remote set-url github "$github_url" 85 + echo "✓ github → $github_url (was: $current_github)" 86 + else 87 + echo "✓ github → $current_github" 88 + fi 89 89 90 - # Set default push to origin 91 - ${pkgs.git}/bin/git config branch.$BRANCH.remote origin 2>/dev/null || true 90 + # Set default push to origin 91 + ${pkgs.git}/bin/git config branch.$BRANCH.remote origin 2>/dev/null || true 92 92 93 - echo 94 - ${pkgs.git}/bin/git remote -v 93 + echo 94 + ${pkgs.git}/bin/git remote -v 95 95 ''; 96 96 97 97 assh = pkgs.writeShellScriptBin "assh" '' ··· 105 105 fi 106 106 107 107 ${pkgs.gum}/bin/gum style --foreground 212 "Connecting to $host:$port (auto-reconnect enabled)..." 108 - 108 + 109 109 while true; do 110 110 ${pkgs.openssh}/bin/ssh -p "$port" -o "BatchMode yes" "$host" || { 111 111 ${pkgs.gum}/bin/gum style --foreground 214 "Connection lost. Reconnecting in 1s..." ··· 207 207 ''; 208 208 209 209 now = pkgs.writeShellScriptBin "now" '' 210 - # Post AtProto status updates 211 - message="" 212 - prompt_message=true 210 + # Post AtProto status updates 211 + message="" 212 + prompt_message=true 213 213 214 - # Parse arguments 215 - while [[ $# -gt 0 ]]; do 216 - case "$1" in 217 - -m|--message) 218 - message="$2" 219 - prompt_message=false 220 - shift 2 221 - ;; 222 - *) 223 - ${pkgs.gum}/bin/gum style --foreground 196 "Usage: now [-m|--message \"your message\"]" 214 + # Parse arguments 215 + while [[ $# -gt 0 ]]; do 216 + case "$1" in 217 + -m|--message) 218 + message="$2" 219 + prompt_message=false 220 + shift 2 221 + ;; 222 + *) 223 + ${pkgs.gum}/bin/gum style --foreground 196 "Usage: now [-m|--message \"your message\"]" 224 + exit 1 225 + ;; 226 + esac 227 + done 228 + 229 + # Load account information from agenix secrets 230 + if [[ -f "/run/agenix/bluesky" ]]; then 231 + source "/run/agenix/bluesky" 232 + else 233 + ${pkgs.gum}/bin/gum style --foreground 196 "Error: Bluesky credentials file not found at /run/agenix/bluesky" 224 234 exit 1 225 - ;; 226 - esac 227 - done 235 + fi 228 236 229 - # Load account information from agenix secrets 230 - if [[ -f "/run/agenix/bluesky" ]]; then 231 - source "/run/agenix/bluesky" 232 - else 233 - ${pkgs.gum}/bin/gum style --foreground 196 "Error: Bluesky credentials file not found at /run/agenix/bluesky" 234 - exit 1 235 - fi 237 + # Prompt for message if none provided 238 + if [[ "$prompt_message" = true ]]; then 239 + message=$(${pkgs.gum}/bin/gum input --placeholder "What's happening?" --prompt "$ACCOUNT1 is: ") 240 + if [[ -z "$message" ]]; then 241 + ${pkgs.gum}/bin/gum style --foreground 214 "No message provided. Aborting." 242 + exit 1 243 + fi 244 + fi 236 245 237 - # Prompt for message if none provided 238 - if [[ "$prompt_message" = true ]]; then 239 - message=$(${pkgs.gum}/bin/gum input --placeholder "What's happening?" --prompt "$ACCOUNT1 is: ") 240 - if [[ -z "$message" ]]; then 241 - ${pkgs.gum}/bin/gum style --foreground 214 "No message provided. Aborting." 242 - exit 1 243 - fi 244 - fi 246 + ${pkgs.gum}/bin/gum spin --spinner dot --title "Posting to Bluesky..." -- /bin/bash <<EOF 247 + # Function to resolve DID to PDS endpoint 248 + resolve_pds() { 249 + local identifier="\$1" 250 + local did="" 245 251 246 - ${pkgs.gum}/bin/gum spin --spinner dot --title "Posting to Bluesky..." -- /bin/bash <<EOF 247 - # Function to resolve DID to PDS endpoint 248 - resolve_pds() { 249 - local identifier="\$1" 250 - local did="" 252 + # If identifier is a handle, resolve to DID first 253 + if [[ ! "\$identifier" =~ ^did: ]]; then 254 + # Try to resolve handle via DNS first, fallback to bsky.social 255 + did=\$(${pkgs.curl}/bin/curl -sf "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=\$identifier" | ${pkgs.jq}/bin/jq -r '.did // empty') 256 + if [[ -z "\$did" ]]; then 257 + echo "Failed to resolve handle: \$identifier" >&2 258 + return 1 259 + fi 260 + else 261 + did="\$identifier" 262 + fi 251 263 252 - # If identifier is a handle, resolve to DID first 253 - if [[ ! "\$identifier" =~ ^did: ]]; then 254 - # Try to resolve handle via DNS first, fallback to bsky.social 255 - did=\$(${pkgs.curl}/bin/curl -sf "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=\$identifier" | ${pkgs.jq}/bin/jq -r '.did // empty') 256 - if [[ -z "\$did" ]]; then 257 - echo "Failed to resolve handle: \$identifier" >&2 258 - return 1 259 - fi 260 - else 261 - did="\$identifier" 262 - fi 264 + # Resolve DID document 265 + local pds_endpoint="" 266 + if [[ "\$did" =~ ^did:plc: ]]; then 267 + # Resolve via PLC directory 268 + pds_endpoint=\$(${pkgs.curl}/bin/curl -sf "https://plc.directory/\$did" | ${pkgs.jq}/bin/jq -r '.service[] | select(.type == "AtprotoPersonalDataServer") | .serviceEndpoint' | head -n1) 269 + elif [[ "\$did" =~ ^did:web: ]]; then 270 + # Resolve via did:web 271 + local domain="\''${did#did:web:}" 272 + pds_endpoint=\$(${pkgs.curl}/bin/curl -sf "https://\$domain/.well-known/did.json" | ${pkgs.jq}/bin/jq -r '.service[] | select(.type == "AtprotoPersonalDataServer") | .serviceEndpoint' | head -n1) 273 + else 274 + echo "Unsupported DID method: \$did" >&2 275 + return 1 276 + fi 263 277 264 - # Resolve DID document 265 - local pds_endpoint="" 266 - if [[ "\$did" =~ ^did:plc: ]]; then 267 - # Resolve via PLC directory 268 - pds_endpoint=\$(${pkgs.curl}/bin/curl -sf "https://plc.directory/\$did" | ${pkgs.jq}/bin/jq -r '.service[] | select(.type == "AtprotoPersonalDataServer") | .serviceEndpoint' | head -n1) 269 - elif [[ "\$did" =~ ^did:web: ]]; then 270 - # Resolve via did:web 271 - local domain="\''${did#did:web:}" 272 - pds_endpoint=\$(${pkgs.curl}/bin/curl -sf "https://\$domain/.well-known/did.json" | ${pkgs.jq}/bin/jq -r '.service[] | select(.type == "AtprotoPersonalDataServer") | .serviceEndpoint' | head -n1) 273 - else 274 - echo "Unsupported DID method: \$did" >&2 275 - return 1 276 - fi 278 + if [[ -z "\$pds_endpoint" ]]; then 279 + echo "Failed to resolve PDS endpoint for: \$did" >&2 280 + return 1 281 + fi 277 282 278 - if [[ -z "\$pds_endpoint" ]]; then 279 - echo "Failed to resolve PDS endpoint for: \$did" >&2 280 - return 1 281 - fi 283 + echo "\$pds_endpoint" 284 + } 282 285 283 - echo "\$pds_endpoint" 284 - } 286 + # Resolve PDS endpoints for both accounts 287 + account1_pds=\$(resolve_pds "$ACCOUNT1") 288 + if [[ -z "\$account1_pds" ]]; then 289 + echo "Failed to resolve PDS for $ACCOUNT1" >&2 290 + exit 1 291 + fi 285 292 286 - # Resolve PDS endpoints for both accounts 287 - account1_pds=\$(resolve_pds "$ACCOUNT1") 288 - if [[ -z "\$account1_pds" ]]; then 289 - echo "Failed to resolve PDS for $ACCOUNT1" >&2 290 - exit 1 291 - fi 293 + account2_pds=\$(resolve_pds "$ACCOUNT2") 294 + if [[ -z "\$account2_pds" ]]; then 295 + echo "Failed to resolve PDS for $ACCOUNT2" >&2 296 + exit 1 297 + fi 292 298 293 - account2_pds=\$(resolve_pds "$ACCOUNT2") 294 - if [[ -z "\$account2_pds" ]]; then 295 - echo "Failed to resolve PDS for $ACCOUNT2" >&2 296 - exit 1 297 - fi 299 + # Generate JWT for ACCOUNT1 300 + account1_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 301 + -H "Content-Type: application/json" \ 302 + -d '{ 303 + "identifier": "'$ACCOUNT1'", 304 + "password": "'$ACCOUNT1_PASSWORD'" 305 + }' \ 306 + "\$account1_pds/xrpc/com.atproto.server.createSession") 298 307 299 - # Generate JWT for ACCOUNT1 300 - account1_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 301 - -H "Content-Type: application/json" \ 302 - -d '{ 303 - "identifier": "'$ACCOUNT1'", 304 - "password": "'$ACCOUNT1_PASSWORD'" 305 - }' \ 306 - "\$account1_pds/xrpc/com.atproto.server.createSession") 308 + account1_jwt=\$(echo "\$account1_response" | ${pkgs.jq}/bin/jq -r '.accessJwt') 309 + account1_did=\$(echo "\$account1_response" | ${pkgs.jq}/bin/jq -r '.did') 307 310 308 - account1_jwt=\$(echo "\$account1_response" | ${pkgs.jq}/bin/jq -r '.accessJwt') 309 - account1_did=\$(echo "\$account1_response" | ${pkgs.jq}/bin/jq -r '.did') 311 + if [[ -z "\$account1_jwt" || "\$account1_jwt" == "null" ]]; then 312 + echo "Failed to authenticate account $ACCOUNT1" >&2 313 + echo "Response: \$account1_response" >&2 314 + exit 1 315 + fi 310 316 311 - if [[ -z "\$account1_jwt" || "\$account1_jwt" == "null" ]]; then 312 - echo "Failed to authenticate account $ACCOUNT1" >&2 313 - echo "Response: \$account1_response" >&2 314 - exit 1 315 - fi 317 + # Generate JWT for ACCOUNT2 318 + account2_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 319 + -H "Content-Type: application/json" \ 320 + -d '{ 321 + "identifier": "'$ACCOUNT2'", 322 + "password": "'$ACCOUNT2_PASSWORD'" 323 + }' \ 324 + "\$account2_pds/xrpc/com.atproto.server.createSession") 316 325 317 - # Generate JWT for ACCOUNT2 318 - account2_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 319 - -H "Content-Type: application/json" \ 320 - -d '{ 321 - "identifier": "'$ACCOUNT2'", 322 - "password": "'$ACCOUNT2_PASSWORD'" 323 - }' \ 324 - "\$account2_pds/xrpc/com.atproto.server.createSession") 326 + account2_jwt=\$(echo "\$account2_response" | ${pkgs.jq}/bin/jq -r '.accessJwt') 327 + account2_did=\$(echo "\$account2_response" | ${pkgs.jq}/bin/jq -r '.did') 325 328 326 - account2_jwt=\$(echo "\$account2_response" | ${pkgs.jq}/bin/jq -r '.accessJwt') 327 - account2_did=\$(echo "\$account2_response" | ${pkgs.jq}/bin/jq -r '.did') 329 + if [[ -z "\$account2_jwt" || "\$account2_jwt" == "null" ]]; then 330 + echo "Failed to authenticate account $ACCOUNT2" >&2 331 + echo "Response: \$account2_response" >&2 332 + exit 1 333 + fi 328 334 329 - if [[ -z "\$account2_jwt" || "\$account2_jwt" == "null" ]]; then 330 - echo "Failed to authenticate account $ACCOUNT2" >&2 331 - echo "Response: \$account2_response" >&2 332 - exit 1 333 - fi 335 + # Post to ACCOUNT1 as a.status.updates 336 + account1_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 337 + -H "Content-Type: application/json" \ 338 + -H "Authorization: Bearer \$account1_jwt" \ 339 + -d '{ 340 + "collection": "a.status.update", 341 + "repo": "'\$account1_did'", 342 + "record": { 343 + "\$type": "a.status.update", 344 + "text": "'"$message"'", 345 + "createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 346 + } 347 + }' \ 348 + "\$account1_pds/xrpc/com.atproto.repo.createRecord") 334 349 335 - # Post to ACCOUNT1 as a.status.updates 336 - account1_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 337 - -H "Content-Type: application/json" \ 338 - -H "Authorization: Bearer \$account1_jwt" \ 339 - -d '{ 340 - "collection": "a.status.update", 341 - "repo": "'\$account1_did'", 342 - "record": { 343 - "\$type": "a.status.update", 344 - "text": "'"$message"'", 345 - "createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 346 - } 347 - }' \ 348 - "\$account1_pds/xrpc/com.atproto.repo.createRecord") 349 - 350 - if [[ \$(echo "\$account1_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then 351 - echo "Error posting to $ACCOUNT1:" >&2 352 - echo "\$account1_post_response" | ${pkgs.jq}/bin/jq >&2 353 - exit 1 354 - fi 350 + if [[ \$(echo "\$account1_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then 351 + echo "Error posting to $ACCOUNT1:" >&2 352 + echo "\$account1_post_response" | ${pkgs.jq}/bin/jq >&2 353 + exit 1 354 + fi 355 355 356 - # Post to ACCOUNT2 as normal post 357 - account2_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 358 - -H "Content-Type: application/json" \ 359 - -H "Authorization: Bearer \$account2_jwt" \ 360 - -d '{ 361 - "collection": "app.bsky.feed.post", 362 - "repo": "'\$account2_did'", 363 - "record": { 364 - "\$type": "app.bsky.feed.post", 365 - "text": "'"$message"'", 366 - "createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 367 - } 368 - }' \ 369 - "\$account2_pds/xrpc/com.atproto.repo.createRecord") 356 + # Post to ACCOUNT2 as normal post 357 + account2_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \ 358 + -H "Content-Type: application/json" \ 359 + -H "Authorization: Bearer \$account2_jwt" \ 360 + -d '{ 361 + "collection": "app.bsky.feed.post", 362 + "repo": "'\$account2_did'", 363 + "record": { 364 + "\$type": "app.bsky.feed.post", 365 + "text": "'"$message"'", 366 + "createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 367 + } 368 + }' \ 369 + "\$account2_pds/xrpc/com.atproto.repo.createRecord") 370 370 371 - if [[ \$(echo "\$account2_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then 372 - echo "Error posting to $ACCOUNT2:" >&2 373 - echo "\$account2_post_response" | ${pkgs.jq}/bin/jq >&2 374 - exit 1 375 - fi 376 - EOF 371 + if [[ \$(echo "\$account2_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then 372 + echo "Error posting to $ACCOUNT2:" >&2 373 + echo "\$account2_post_response" | ${pkgs.jq}/bin/jq >&2 374 + exit 1 375 + fi 376 + EOF 377 377 378 - if [[ $? -eq 0 ]]; then 379 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ Posted successfully!" 380 - else 381 - ${pkgs.gum}/bin/gum style --foreground 196 "✗ Failed to post" 382 - exit 1 383 - fi 378 + if [[ $? -eq 0 ]]; then 379 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ Posted successfully!" 380 + else 381 + ${pkgs.gum}/bin/gum style --foreground 196 "✗ Failed to post" 382 + exit 1 383 + fi 384 384 ''; 385 385 386 386 ghostty-setup = pkgs.writeShellScriptBin "ghostty-setup" '' ··· 422 422 ''; 423 423 424 424 ghrpc = pkgs.writeShellScriptBin "ghrpc" '' 425 - set -euo pipefail 425 + set -euo pipefail 426 426 427 - # Defaults (configured by Nix) 428 - PLC_ID="${tangled.plcId}" 429 - GITHUB_USER="${tangled.githubUser}" 430 - KNOT_HOST="${tangled.knotHost}" 431 - TANGLED_DOMAIN="${tangled.domain}" 432 - BRANCH="${tangled.defaultBranch}" 433 - VISIBILITY="public" 434 - DESCRIPTION="" 435 - GITHUB=true 436 - TANGLED=true 437 - NAME="" 427 + # Defaults (configured by Nix) 428 + PLC_ID="${tangled.plcId}" 429 + GITHUB_USER="${tangled.githubUser}" 430 + KNOT_HOST="${tangled.knotHost}" 431 + TANGLED_DOMAIN="${tangled.domain}" 432 + BRANCH="${tangled.defaultBranch}" 433 + VISIBILITY="public" 434 + DESCRIPTION="" 435 + GITHUB=true 436 + TANGLED=true 437 + NAME="" 438 438 439 - usage() { 440 - cat <<EOF 441 - Usage: ghrpc [OPTIONS] [NAME] 439 + usage() { 440 + cat <<EOF 441 + Usage: ghrpc [OPTIONS] [NAME] 442 442 443 - Create repositories on GitHub and/or Tangled. 444 - Remotes: origin → knot (tangled), github → GitHub 443 + Create repositories on GitHub and/or Tangled. 444 + Remotes: origin → knot (tangled), github → GitHub 445 445 446 - Arguments: 447 - NAME Repository name (defaults to current directory name) 446 + Arguments: 447 + NAME Repository name (defaults to current directory name) 448 448 449 - Options: 450 - -d, --description STR Repository description 451 - -p, --public Make repository public (default) 452 - --private Make repository private 453 - -g, --github-only Only create on GitHub 454 - -t, --tangled-only Only create on Tangled 455 - --no-github Skip GitHub 456 - --no-tangled Skip Tangled 457 - --plc ID PLC ID (default: $PLC_ID) 458 - --domain DOMAIN Tangled domain (default: $TANGLED_DOMAIN) 459 - -h, --help Show this help 460 - EOF 461 - exit 0 462 - } 449 + Options: 450 + -d, --description STR Repository description 451 + -p, --public Make repository public (default) 452 + --private Make repository private 453 + -g, --github-only Only create on GitHub 454 + -t, --tangled-only Only create on Tangled 455 + --no-github Skip GitHub 456 + --no-tangled Skip Tangled 457 + --plc ID PLC ID (default: $PLC_ID) 458 + --domain DOMAIN Tangled domain (default: $TANGLED_DOMAIN) 459 + -h, --help Show this help 460 + EOF 461 + exit 0 462 + } 463 463 464 - while [[ $# -gt 0 ]]; do 465 - case "$1" in 466 - -h|--help) usage ;; 467 - -d|--description) DESCRIPTION="$2"; shift 2 ;; 468 - -p|--public) VISIBILITY="public"; shift ;; 469 - --private) VISIBILITY="private"; shift ;; 470 - -g|--github-only) TANGLED=false; shift ;; 471 - -t|--tangled-only) GITHUB=false; shift ;; 472 - --no-github) GITHUB=false; shift ;; 473 - --no-tangled) TANGLED=false; shift ;; 474 - --plc) PLC_ID="$2"; shift 2 ;; 475 - --domain) TANGLED_DOMAIN="$2"; shift 2 ;; 476 - -*) echo "Unknown option: $1" >&2; exit 1 ;; 477 - *) NAME="$1"; shift ;; 478 - esac 479 - done 464 + while [[ $# -gt 0 ]]; do 465 + case "$1" in 466 + -h|--help) usage ;; 467 + -d|--description) DESCRIPTION="$2"; shift 2 ;; 468 + -p|--public) VISIBILITY="public"; shift ;; 469 + --private) VISIBILITY="private"; shift ;; 470 + -g|--github-only) TANGLED=false; shift ;; 471 + -t|--tangled-only) GITHUB=false; shift ;; 472 + --no-github) GITHUB=false; shift ;; 473 + --no-tangled) TANGLED=false; shift ;; 474 + --plc) PLC_ID="$2"; shift 2 ;; 475 + --domain) TANGLED_DOMAIN="$2"; shift 2 ;; 476 + -*) echo "Unknown option: $1" >&2; exit 1 ;; 477 + *) NAME="$1"; shift ;; 478 + esac 479 + done 480 480 481 - # Determine repo name 482 - if [[ -z "$NAME" ]]; then 483 - if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 484 - NAME=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)") 485 - else 486 - read -p "Repository name: " NAME 481 + # Determine repo name 487 482 if [[ -z "$NAME" ]]; then 488 - echo "Error: Repository name is required" >&2 489 - exit 1 483 + if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 484 + NAME=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)") 485 + else 486 + read -p "Repository name: " NAME 487 + if [[ -z "$NAME" ]]; then 488 + echo "Error: Repository name is required" >&2 489 + exit 1 490 + fi 491 + fi 490 492 fi 491 - fi 492 - fi 493 493 494 - # Prompt for description if not provided 495 - if [[ -z "$DESCRIPTION" ]]; then 496 - read -p "Description (optional): " DESCRIPTION 497 - fi 494 + # Prompt for description if not provided 495 + if [[ -z "$DESCRIPTION" ]]; then 496 + read -p "Description (optional): " DESCRIPTION 497 + fi 498 498 499 - echo "Creating repository: $NAME" 499 + echo "Creating repository: $NAME" 500 500 501 - # Create on Tangled 502 - if [[ "$TANGLED" == true ]]; then 503 - tangled_cookie="" 504 - if [[ -f "/run/agenix/tangled-session" ]]; then 505 - tangled_cookie=$(cat /run/agenix/tangled-session) 506 - fi 501 + # Create on Tangled 502 + if [[ "$TANGLED" == true ]]; then 503 + tangled_cookie="" 504 + if [[ -f "/run/agenix/tangled-session" ]]; then 505 + tangled_cookie=$(cat /run/agenix/tangled-session) 506 + fi 507 507 508 - if [[ -z "$tangled_cookie" ]]; then 509 - echo "Warning: No tangled session cookie found at /run/agenix/tangled-session" >&2 510 - else 511 - encoded_desc=$(printf '%s' "$DESCRIPTION" | ${pkgs.gnused}/bin/sed 's/ /%20/g; s/!/%21/g; s/"/%22/g; s/#/%23/g; s/\$/%24/g; s/&/%26/g; s/'"'"'/%27/g; s/(/%28/g; s/)/%29/g; s/\*/%2A/g; s/+/%2B/g; s/,/%2C/g; s/\//%2F/g; s/:/%3A/g; s/;/%3B/g; s/=/%3D/g; s/?/%3F/g; s/@/%40/g; s/\[/%5B/g; s/\]/%5D/g') 508 + if [[ -z "$tangled_cookie" ]]; then 509 + echo "Warning: No tangled session cookie found at /run/agenix/tangled-session" >&2 510 + else 511 + encoded_desc=$(printf '%s' "$DESCRIPTION" | ${pkgs.gnused}/bin/sed 's/ /%20/g; s/!/%21/g; s/"/%22/g; s/#/%23/g; s/\$/%24/g; s/&/%26/g; s/'"'"'/%27/g; s/(/%28/g; s/)/%29/g; s/\*/%2A/g; s/+/%2B/g; s/,/%2C/g; s/\//%2F/g; s/:/%3A/g; s/;/%3B/g; s/=/%3D/g; s/?/%3F/g; s/@/%40/g; s/\[/%5B/g; s/\]/%5D/g') 512 512 513 - response=$(${pkgs.curl}/bin/curl -s 'https://tangled.org/repo/new' \ 514 - -H 'Accept: */*' \ 515 - -H 'Content-Type: application/x-www-form-urlencoded' \ 516 - -b "appview-session-v2=$tangled_cookie" \ 517 - -H 'HX-Request: true' \ 518 - -H 'Origin: https://tangled.org' \ 519 - --data-raw "name=$NAME&description=$encoded_desc&branch=$BRANCH&domain=$TANGLED_DOMAIN") 513 + response=$(${pkgs.curl}/bin/curl -s 'https://tangled.org/repo/new' \ 514 + -H 'Accept: */*' \ 515 + -H 'Content-Type: application/x-www-form-urlencoded' \ 516 + -b "appview-session-v2=$tangled_cookie" \ 517 + -H 'HX-Request: true' \ 518 + -H 'Origin: https://tangled.org' \ 519 + --data-raw "name=$NAME&description=$encoded_desc&branch=$BRANCH&domain=$TANGLED_DOMAIN") 520 520 521 - if echo "$response" | grep -qi "error\|failed"; then 522 - echo "✗ Failed to create Tangled repository" >&2 523 - else 524 - echo "✓ Tangled: https://tangled.org/$TANGLED_DOMAIN/$NAME" 521 + if echo "$response" | grep -qi "error\|failed"; then 522 + echo "✗ Failed to create Tangled repository" >&2 523 + else 524 + echo "✓ Tangled: https://tangled.org/$TANGLED_DOMAIN/$NAME" 525 + fi 526 + fi 525 527 fi 526 - fi 527 - fi 528 528 529 - # Create on GitHub 530 - if [[ "$GITHUB" == true ]]; then 531 - gh_flags="--$VISIBILITY" 532 - [[ -n "$DESCRIPTION" ]] && gh_flags="$gh_flags --description \"$DESCRIPTION\"" 529 + # Create on GitHub 530 + if [[ "$GITHUB" == true ]]; then 531 + gh_flags="--$VISIBILITY" 532 + [[ -n "$DESCRIPTION" ]] && gh_flags="$gh_flags --description \"$DESCRIPTION\"" 533 533 534 - if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 535 - if eval "${pkgs.gh}/bin/gh repo create \"$NAME\" $gh_flags --source=. --push --remote=github 2>/dev/null"; then 536 - echo "✓ GitHub: https://github.com/$GITHUB_USER/$NAME" 537 - else 538 - echo "✗ Failed to create GitHub repository" >&2 534 + if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 535 + if eval "${pkgs.gh}/bin/gh repo create \"$NAME\" $gh_flags --source=. --push --remote=github 2>/dev/null"; then 536 + echo "✓ GitHub: https://github.com/$GITHUB_USER/$NAME" 537 + else 538 + echo "✗ Failed to create GitHub repository" >&2 539 + fi 540 + else 541 + if eval "${pkgs.gh}/bin/gh repo create \"$NAME\" $gh_flags --clone 2>/dev/null"; then 542 + echo "✓ GitHub: created and cloned $NAME" 543 + cd "$NAME" 544 + else 545 + echo "✗ Failed to create GitHub repository" >&2 546 + fi 547 + fi 539 548 fi 540 - else 541 - if eval "${pkgs.gh}/bin/gh repo create \"$NAME\" $gh_flags --clone 2>/dev/null"; then 542 - echo "✓ GitHub: created and cloned $NAME" 543 - cd "$NAME" 544 - else 545 - echo "✗ Failed to create GitHub repository" >&2 546 - fi 547 - fi 548 - fi 549 549 550 - # Configure remotes: origin → knot, github → GitHub 551 - if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 552 - knot_url="git@$KNOT_HOST:$PLC_ID/$NAME" 553 - github_url="git@github.com:$GITHUB_USER/$NAME.git" 550 + # Configure remotes: origin → knot, github → GitHub 551 + if ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 552 + knot_url="git@$KNOT_HOST:$PLC_ID/$NAME" 553 + github_url="git@github.com:$GITHUB_USER/$NAME.git" 554 554 555 - # Set origin to knot 556 - if [[ "$TANGLED" == true ]]; then 557 - if ${pkgs.git}/bin/git remote get-url origin &>/dev/null; then 558 - current_origin=$(${pkgs.git}/bin/git remote get-url origin) 559 - if [[ "$current_origin" != *"$KNOT_HOST"* ]]; then 560 - ${pkgs.git}/bin/git remote set-url origin "$knot_url" 555 + # Set origin to knot 556 + if [[ "$TANGLED" == true ]]; then 557 + if ${pkgs.git}/bin/git remote get-url origin &>/dev/null; then 558 + current_origin=$(${pkgs.git}/bin/git remote get-url origin) 559 + if [[ "$current_origin" != *"$KNOT_HOST"* ]]; then 560 + ${pkgs.git}/bin/git remote set-url origin "$knot_url" 561 + fi 562 + else 563 + ${pkgs.git}/bin/git remote add origin "$knot_url" 564 + fi 561 565 fi 562 - else 563 - ${pkgs.git}/bin/git remote add origin "$knot_url" 564 - fi 565 - fi 566 566 567 - # Set github remote 568 - if [[ "$GITHUB" == true ]]; then 569 - ${pkgs.git}/bin/git remote add github "$github_url" 2>/dev/null || \ 570 - ${pkgs.git}/bin/git remote set-url github "$github_url" 571 - fi 567 + # Set github remote 568 + if [[ "$GITHUB" == true ]]; then 569 + ${pkgs.git}/bin/git remote add github "$github_url" 2>/dev/null || \ 570 + ${pkgs.git}/bin/git remote set-url github "$github_url" 571 + fi 572 572 573 - # Set default push to origin (knot) 574 - ${pkgs.git}/bin/git config branch.$BRANCH.remote origin 2>/dev/null || true 573 + # Set default push to origin (knot) 574 + ${pkgs.git}/bin/git config branch.$BRANCH.remote origin 2>/dev/null || true 575 575 576 - echo 577 - ${pkgs.git}/bin/git remote -v 578 - fi 576 + echo 577 + ${pkgs.git}/bin/git remote -v 578 + fi 579 579 ''; 580 580 581 581 in ··· 754 754 vim = "nvim"; 755 755 }; 756 756 initContent = '' 757 - zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' 758 - zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" 759 - zstyle ':completion:*' menu no 760 - zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' 761 - zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' 757 + zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' 758 + zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" 759 + zstyle ':completion:*' menu no 760 + zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' 761 + zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' 762 762 763 - eval "$(terminal-wakatime init)" 763 + eval "$(terminal-wakatime init)" 764 764 765 - # Edit command buffer in $EDITOR (Ctrl+X, Ctrl+E) 766 - autoload -Uz edit-command-line 767 - zle -N edit-command-line 768 - bindkey '^X^E' edit-command-line 765 + # Edit command buffer in $EDITOR (Ctrl+X, Ctrl+E) 766 + autoload -Uz edit-command-line 767 + zle -N edit-command-line 768 + bindkey '^X^E' edit-command-line 769 769 770 - # Magic space - expand history expressions like !! or !$ 771 - bindkey ' ' magic-space 770 + # Magic space - expand history expressions like !! or !$ 771 + bindkey ' ' magic-space 772 772 773 - # Suffix aliases - open files by extension 774 - alias -s json=jless 775 - alias -s md=bat 776 - alias -s go='$EDITOR' 777 - alias -s rs='$EDITOR' 778 - alias -s txt=bat 779 - alias -s log=bat 780 - alias -s py='$EDITOR' 781 - alias -s js='$EDITOR' 782 - alias -s ts='$EDITOR' 783 - ${if pkgs.stdenv.isDarwin then "alias -s html=open" else ""} 773 + # Suffix aliases - open files by extension 774 + alias -s json=jless 775 + alias -s md=bat 776 + alias -s go='$EDITOR' 777 + alias -s rs='$EDITOR' 778 + alias -s txt=bat 779 + alias -s log=bat 780 + alias -s py='$EDITOR' 781 + alias -s js='$EDITOR' 782 + alias -s ts='$EDITOR' 783 + ${if pkgs.stdenv.isDarwin then "alias -s html=open" else ""} 784 784 785 - # Global aliases 786 - alias -g NE='2>/dev/null' 787 - alias -g NO='>/dev/null' 788 - alias -g NUL='>/dev/null 2>&1' 789 - alias -g J='| jq' 785 + # Global aliases 786 + alias -g NE='2>/dev/null' 787 + alias -g NO='>/dev/null' 788 + alias -g NUL='>/dev/null 2>&1' 789 + alias -g J='| jq' 790 790 791 - # OSC 52 clipboard (works over SSH) 792 - function osc52copy() { 793 - local data=$(cat "$@" | base64 | tr -d '\n') 794 - printf "\033]52;c;%s\a" "$data" 795 - } 796 - alias -g C='| osc52copy' 791 + # OSC 52 clipboard (works over SSH) 792 + function osc52copy() { 793 + local data=$(cat "$@" | base64 | tr -d '\n') 794 + printf "\033]52;c;%s\a" "$data" 795 + } 796 + alias -g C='| osc52copy' 797 797 798 - # zmv - advanced batch rename/move 799 - autoload -Uz zmv 800 - alias zcp='zmv -C' 801 - alias zln='zmv -L' 798 + # zmv - advanced batch rename/move 799 + autoload -Uz zmv 800 + alias zcp='zmv -C' 801 + alias zln='zmv -L' 802 802 803 - # Clear screen but keep current command buffer (Ctrl+X, Ctrl+L) 804 - function clear-screen-and-scrollback() { 805 - echoti civis >"$TTY" 806 - printf '%b' '\e[H\e[2J\e[3J' >"$TTY" 807 - echoti cnorm >"$TTY" 808 - zle redisplay 809 - } 810 - zle -N clear-screen-and-scrollback 811 - bindkey '^X^L' clear-screen-and-scrollback 803 + # Clear screen but keep current command buffer (Ctrl+X, Ctrl+L) 804 + function clear-screen-and-scrollback() { 805 + echoti civis >"$TTY" 806 + printf '%b' '\e[H\e[2J\e[3J' >"$TTY" 807 + echoti cnorm >"$TTY" 808 + zle redisplay 809 + } 810 + zle -N clear-screen-and-scrollback 811 + bindkey '^X^L' clear-screen-and-scrollback 812 812 813 - # Copy current command buffer to clipboard (Ctrl+X, Ctrl+C) - OSC 52 for SSH support 814 - function copy-buffer-to-clipboard() { 815 - local data=$(echo -n "$BUFFER" | base64 | tr -d '\n') 816 - printf "\033]52;c;%s\a" "$data" 817 - zle -M "Copied to clipboard" 818 - } 819 - zle -N copy-buffer-to-clipboard 820 - bindkey '^X^C' copy-buffer-to-clipboard 813 + # Copy current command buffer to clipboard (Ctrl+X, Ctrl+C) - OSC 52 for SSH support 814 + function copy-buffer-to-clipboard() { 815 + local data=$(echo -n "$BUFFER" | base64 | tr -d '\n') 816 + printf "\033]52;c;%s\a" "$data" 817 + zle -M "Copied to clipboard" 818 + } 819 + zle -N copy-buffer-to-clipboard 820 + bindkey '^X^C' copy-buffer-to-clipboard 821 821 822 - # chpwd hooks 823 - autoload -Uz add-zsh-hook 822 + # chpwd hooks 823 + autoload -Uz add-zsh-hook 824 824 825 - function auto_venv() { 826 - if [[ -n "$VIRTUAL_ENV" && ! -f "$VIRTUAL_ENV/bin/activate" ]]; then 827 - deactivate 828 - fi 829 - [[ -n "$VIRTUAL_ENV" ]] && return 830 - local dir="$PWD" 831 - while [[ "$dir" != "/" ]]; do 832 - if [[ -f "$dir/.venv/bin/activate" ]]; then 833 - source "$dir/.venv/bin/activate" 834 - return 835 - fi 836 - dir="''${dir:h}" 837 - done 838 - } 825 + function auto_venv() { 826 + if [[ -n "$VIRTUAL_ENV" && ! -f "$VIRTUAL_ENV/bin/activate" ]]; then 827 + deactivate 828 + fi 829 + [[ -n "$VIRTUAL_ENV" ]] && return 830 + local dir="$PWD" 831 + while [[ "$dir" != "/" ]]; do 832 + if [[ -f "$dir/.venv/bin/activate" ]]; then 833 + source "$dir/.venv/bin/activate" 834 + return 835 + fi 836 + dir="''${dir:h}" 837 + done 838 + } 839 839 840 - function auto_nix() { 841 - [[ -n "$IN_NIX_SHELL" ]] && return 842 - local dir="$PWD" 843 - while [[ "$dir" != "/" ]]; do 844 - if [[ -f "$dir/flake.nix" ]]; then 845 - if [[ ! -f "$dir/.envrc" ]]; then 846 - cat > "$dir/.envrc" <<'EOF' 847 - eval "$(nix print-dev-env)" 848 - EOF 849 - command direnv allow "$dir" >/dev/null 2>&1 850 - fi 851 - command direnv reload >/dev/null 2>&1 852 - return 853 - fi 854 - dir="''${dir:h}" 855 - done 856 - } 840 + function auto_nix() { 841 + [[ -n "$IN_NIX_SHELL" ]] && return 842 + local dir="$PWD" 843 + while [[ "$dir" != "/" ]]; do 844 + if [[ -f "$dir/flake.nix" ]]; then 845 + if [[ ! -f "$dir/.envrc" ]]; then 846 + cat > "$dir/.envrc" <<'EOF' 847 + use flake 848 + EOF 849 + command direnv allow "$dir" >/dev/null 2>&1 850 + fi 851 + command direnv reload >/dev/null 2>&1 852 + return 853 + fi 854 + dir="''${dir:h}" 855 + done 856 + } 857 857 858 - add-zsh-hook chpwd auto_venv 859 - add-zsh-hook chpwd auto_nix 858 + add-zsh-hook chpwd auto_venv 859 + add-zsh-hook chpwd auto_nix 860 860 ''; 861 861 history = { 862 862 size = 10000;