This commit is contained in:
David Aizenberg
2025-12-14 03:24:35 +01:00
parent b309851423
commit 11e5fb4e54
15 changed files with 593 additions and 2 deletions

View File

@@ -0,0 +1,285 @@
#!/usr/bin/env python3
"""Quick memo - text input or clipboard capture to jax-bot.
Hotkeys:
F12 - Text input mode (wofi dialog)
SHIFT+F12 - Clipboard mode (text/URL/image)
Usage:
quick-memo --input # Text input via wofi
quick-memo --clipboard # Capture clipboard content
"""
import argparse
import subprocess
import sys
# httpx is optional - fall back to urllib if not available
try:
import httpx
HAS_HTTPX = True
except ImportError:
import urllib.request
import urllib.error
import json as json_module
HAS_HTTPX = False
# Configuration - chezmoi template with default fallback
{{- $jaxUrl := "http://127.0.0.1:8080" -}}
{{- if hasKey . "jaxBotUrl" -}}
{{- $jaxUrl = .jaxBotUrl -}}
{{- end }}
JAX_URL = "{{ $jaxUrl }}"
NOTIFY_ID = "91192"
def notify(title: str, msg: str, critical: bool = False):
"""Send desktop notification."""
cmd = ["notify-send", title, msg, "-r", NOTIFY_ID, "-t", "3000"]
if critical:
cmd.extend(["-u", "critical"])
subprocess.run(cmd, capture_output=True)
def check_jax() -> bool:
"""Check if jax-bot is running."""
try:
if HAS_HTTPX:
httpx.get(f"{JAX_URL}/", timeout=2)
else:
urllib.request.urlopen(f"{JAX_URL}/", timeout=2)
return True
except Exception:
return False
def get_text_input() -> str | None:
"""Open yad text area dialog."""
result = subprocess.run(
[
"yad", "--form",
"--field", ":TXT",
"--title", "Quick Memo",
"--width", "500",
"--height", "300",
"--center",
"--on-top",
"--skip-taskbar",
"--undecorated",
"--borders", "20",
],
capture_output=True,
text=True,
)
if result.returncode != 0:
return None # User cancelled
# yad returns field value followed by |
text = result.stdout.strip()
if text.endswith("|"):
text = text[:-1]
return text.strip()
def get_clipboard_type() -> str:
"""Detect clipboard content type."""
result = subprocess.run(
["wl-paste", "--list-types"],
capture_output=True,
text=True,
)
types = result.stdout.lower()
if "image/png" in types:
return "image/png"
elif "image/jpeg" in types:
return "image/jpeg"
elif "text/plain" in types:
return "text"
return "unknown"
def get_clipboard_text() -> str:
"""Get text from clipboard."""
result = subprocess.run(["wl-paste"], capture_output=True, text=True)
return result.stdout.strip()
def get_clipboard_image(mime: str) -> bytes:
"""Get image bytes from clipboard."""
result = subprocess.run(
["wl-paste", "--type", mime],
capture_output=True,
)
return result.stdout
def send_memo_httpx(
content: str | None = None,
image_data: bytes | None = None,
image_type: str | None = None,
) -> dict:
"""Send memo via httpx."""
try:
if image_data:
ext = "png" if "png" in (image_type or "") else "jpg"
response = httpx.post(
f"{JAX_URL}/api/memo",
files={"file": (f"clipboard.{ext}", image_data, image_type)},
data={"auto_tag": "true"},
timeout=30,
)
else:
is_url = (content or "").startswith(("http://", "https://"))
response = httpx.post(
f"{JAX_URL}/api/memo",
data={
"content": content,
"auto_tag": "true",
"summarize_url": "true" if is_url else "false",
},
timeout=30,
)
return response.json()
except Exception as e:
return {"success": False, "error": str(e)}
def send_memo_urllib(
content: str | None = None,
image_data: bytes | None = None,
image_type: str | None = None,
) -> dict:
"""Send memo via urllib (fallback when httpx unavailable)."""
import io
import uuid
try:
boundary = uuid.uuid4().hex
if image_data:
ext = "png" if "png" in (image_type or "") else "jpg"
filename = f"clipboard.{ext}"
body = io.BytesIO()
body.write(f"--{boundary}\r\n".encode())
body.write(f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode())
body.write(f"Content-Type: {image_type}\r\n\r\n".encode())
body.write(image_data)
body.write(f"\r\n--{boundary}\r\n".encode())
body.write(b'Content-Disposition: form-data; name="auto_tag"\r\n\r\ntrue')
body.write(f"\r\n--{boundary}--\r\n".encode())
req = urllib.request.Request(
f"{JAX_URL}/api/memo",
data=body.getvalue(),
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
else:
is_url = (content or "").startswith(("http://", "https://"))
data = urllib.parse.urlencode({
"content": content or "",
"auto_tag": "true",
"summarize_url": "true" if is_url else "false",
}).encode()
req = urllib.request.Request(f"{JAX_URL}/api/memo", data=data)
with urllib.request.urlopen(req, timeout=30) as response:
return json_module.loads(response.read().decode())
except Exception as e:
return {"success": False, "error": str(e)}
def send_memo(
content: str | None = None,
image_data: bytes | None = None,
image_type: str | None = None,
) -> dict:
"""Send memo to jax-bot API."""
if HAS_HTTPX:
return send_memo_httpx(content, image_data, image_type)
return send_memo_urllib(content, image_data, image_type)
def mode_input():
"""F12: Text input mode via wofi dialog."""
text = get_text_input()
if not text:
return # User cancelled, no notification
notify("Quick Memo", "Processing...")
result = send_memo(content=text)
if result.get("success"):
tags = " ".join(f"#{t}" for t in result.get("tags", []))
preview = text[:60] + ("..." if len(text) > 60 else "")
notify("Quick Memo", f"Saved!\n{preview}\n{tags}")
else:
notify("Quick Memo", f"Error: {result.get('error')}", critical=True)
def mode_clipboard():
"""SHIFT+F12: Clipboard capture mode."""
clip_type = get_clipboard_type()
if clip_type == "unknown":
notify("Quick Memo", "Clipboard empty", critical=True)
return
notify("Quick Memo", "Processing...")
if clip_type.startswith("image/"):
image_data = get_clipboard_image(clip_type)
if not image_data:
notify("Quick Memo", "Failed to read image", critical=True)
return
result = send_memo(image_data=image_data, image_type=clip_type)
else:
text = get_clipboard_text()
if not text:
notify("Quick Memo", "Clipboard empty", critical=True)
return
result = send_memo(content=text)
if result.get("success"):
tags = " ".join(f"#{t}" for t in result.get("tags", []))
preview = result.get("content", "")[:60]
if len(result.get("content", "")) > 60:
preview += "..."
notify("Quick Memo", f"Saved!\n{preview}\n{tags}")
else:
notify("Quick Memo", f"Error: {result.get('error')}", critical=True)
def main():
parser = argparse.ArgumentParser(
description="Quick memo to jax-bot",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"--input", "-i",
action="store_true",
help="Text input mode (wofi dialog)",
)
parser.add_argument(
"--clipboard", "-c",
action="store_true",
help="Clipboard capture mode",
)
args = parser.parse_args()
if not args.input and not args.clipboard:
parser.print_help()
sys.exit(1)
if not check_jax():
notify("Quick Memo", "jax-bot not running", critical=True)
sys.exit(1)
if args.input:
mode_input()
elif args.clipboard:
mode_clipboard()
if __name__ == "__main__":
main()

104
home/dot_tmux.conf Normal file
View File

@@ -0,0 +1,104 @@
# Modern tmux config with sensible defaults
# --- General ---
set -g default-terminal "tmux-256color"
set -g history-limit 1000000
set -g mouse on
set -g focus-events on
# Faster escape time (for vim/TUI apps)
set -sg escape-time 0
set -sg repeat-time 300
# --- Terminal features for modern TUI apps ---
# True color support
set -sa terminal-features ',xterm-ghostty:RGB'
set -sa terminal-features ',xterm-256color:RGB'
set -sa terminal-overrides ',xterm-256color:Tc'
# Extended keys (Ctrl+Shift, etc. for Claude Code, neovim, etc.)
set -g extended-keys on
set -sa terminal-features ',xterm-ghostty:extkeys'
set -sa terminal-features ',xterm-256color:extkeys'
# Clipboard via OSC 52 (works over SSH)
set -g set-clipboard on
set -g allow-passthrough on
set -sa terminal-features ',xterm-ghostty:clipboard'
set -sa terminal-features ',xterm-256color:clipboard'
# Cursor style passthrough (for neovim cursor changes)
set -sa terminal-overrides '*:Ss=\E[%p1%d q:Se=\E[ q'
# Undercurl support (for neovim diagnostics)
set -sa terminal-overrides ',*:Smulx=\E[4::%p1%dm'
set -sa terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m'
# Start windows and panes at 1
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
# --- Prefix ---
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# --- Keybindings ---
# Reload config
bind r source-file ~/.tmux.conf \; display "Config reloaded"
# Split panes (intuitive keys)
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
unbind '"'
unbind %
# Vim-style pane navigation
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# Pane resizing
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# Window navigation
bind -r n next-window
bind -r p previous-window
bind Tab last-window
# Copy mode (vi style)
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
bind -T copy-mode-vi Escape send -X cancel
# --- Status bar ---
set -g status-position top
set -g status-interval 5
set -g status-style 'bg=#1a1a1a fg=#808080'
# Left: session name
set -g status-left-length 30
set -g status-left '#[fg=#c0c0c0,bold] #S #[fg=#404040]|'
# Right: minimal info
set -g status-right-length 50
set -g status-right '#[fg=#606060]%H:%M #[fg=#404040]| #[fg=#808080]%d %b'
# Window status
setw -g window-status-format ' #I:#W '
setw -g window-status-current-format '#[fg=#c0c0c0,bold] #I:#W '
setw -g window-status-separator ''
# Pane borders
set -g pane-border-style 'fg=#303030'
set -g pane-active-border-style 'fg=#606060'
# Messages
set -g message-style 'bg=#1a1a1a fg=#c0c0c0'

View File

@@ -0,0 +1,3 @@
--ozone-platform-hint=auto
--enable-wayland-ime
--wayland-text-input-version=3

View File

@@ -0,0 +1,50 @@
# Man pages with bat
set -x MANROFFOPT "-c"
set -x MANPAGER "sh -c 'col -bx | bat -l man -p'"
# ~/.local/bin
fish_add_path -m $HOME/.local/bin
# LM Studio
fish_add_path $HOME/.lmstudio/bin
# Scaleway (lazy-load)
function scw --wraps=scw
functions -e scw
eval (command scw autocomplete script shell=fish)
command scw $argv
end
# Kubernetes
set -e SHOW_K8S
kubeswitch kubectl-alias kubectl
kubeswitch inherit-env
set -gx KUBECONFIG /dev/null
# History with timestamps
function history
builtin history --show-time='%F %T '
end
# Bash-style !! and !$
function __history_previous_command
switch (commandline -t)
case "!"
commandline -t $history[1]; commandline -f repaint
case "*"
commandline -i !
end
end
function __history_previous_command_arguments
switch (commandline -t)
case "!"
commandline -t ""
commandline -f history-token-search-backward
case "*"
commandline -i '$'
end
end
bind ! __history_previous_command
bind '$' __history_previous_command_arguments

View File

@@ -0,0 +1,6 @@
fabioantunes/fish-nvm
edc/bass
jorgebucaran/fisher
jethrokuan/z
eth-p/fish-kubeswitch
franciscolourenco/done

View File

@@ -0,0 +1,22 @@
function fish_prompt
set -l last_status $status
# Only if the user has explicitly flipped SHOW_K8S on do we fetch & show
if test "$SHOW_K8S" = "1"
# Single kubectl call for both context and namespace
set -l kube_info (kubectl config view --minify --output 'jsonpath={.current-context}/{..namespace}' 2>/dev/null)
if test -n "$kube_info"
# Default namespace if none set
set kube_info (string replace -r '/$' '/default' $kube_info)
echo -n "[k8s:$kube_info] "
end
end
# Red prompt on error, normal otherwise
if test $last_status -ne 0
set_color red
end
printf '%s> ' (prompt_pwd)
set_color normal
end

View File

@@ -0,0 +1,36 @@
function gha-cancel-run --description "Cancel a GitHub Actions run using its URL"
if test -z "$argv[1]"
echo "Usage: gha-cancel-run <GitHub Actions run URL>"
echo "Example: gha-cancel-run https://github.com/OWNER/REPO/actions/runs/123456789"
return 1
end
set -l run_url $argv[1]
# Extract owner, repo, and run_id from the URL using regular expressions
# This regex is specifically designed to parse the run URL format
if not string match --regex 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url"
echo "Error: Invalid GitHub Actions run URL format."
echo "Expected format: https://github.com/OWNER/REPO/actions/runs/RUN_ID"
return 1
end
set -l owner (string match --regex --groups=1 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2]
set -l repo (string match --regex --groups=2 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2]
set -l run_id (string match --regex --groups=3 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2]
echo "Attempting to cancel run ID: $run_id in $owner/$repo..."
gh api \
--method POST \
-H "Accept: application/vnd.github.v3+json" \
"/repos/$owner/$repo/actions/runs/$run_id/cancel"
if test $status -eq 0
echo "Cancellation request sent successfully for run ID: $run_id"
else
echo "Failed to send cancellation request for run ID: $run_id"
echo "Check the error message above for details."
return 1
end
end

View File

@@ -0,0 +1,3 @@
function j --wraps=z
z $argv
end

View File

@@ -0,0 +1,3 @@
function koff --description "Disable k8s prompt"
set -e SHOW_K8S
end

View File

@@ -0,0 +1,4 @@
function ktx --description "Switch k8s context and enable prompt"
kubeswitch ctx $argv
set -g SHOW_K8S 1
end

View File

@@ -0,0 +1,11 @@
function tmpdir --description "Create and cd into a temp directory under ~/tmp"
set -l date (date +%Y%m%d)
if test -n "$argv[1]"
set -l dir ~/tmp/$date-$argv[1]
mkdir -p $dir && cd $dir
else
set -l dir ~/tmp/{$date}_(random)
mkdir -p $dir && cd $dir
end
end

View File

@@ -0,0 +1,3 @@
function tt --wraps=tmpdir
tmpdir $argv
end

View File

@@ -0,0 +1,50 @@
# Ghostty Configuration
# https://ghostty.org/docs/config
# Appearance
background = #141414
background-opacity = 0.9
background-blur-radius = 20
# Window
window-padding-x = 12
window-padding-y = 12
window-padding-balance = true
window-decoration = server
# Font
font-size = 12
font-thicken = true
# Cursor
cursor-style = block
cursor-style-blink = false
# Scrollback
scrollback-limit = 4294967295
# Clipboard (OSC 52 for neovim/tmux/SSH)
copy-on-select = true
clipboard-trim-trailing-spaces = true
clipboard-read = allow
clipboard-write = allow
# Shell integration with SSH support
shell-integration = detect
shell-integration-features = cursor,sudo,title,ssh-env,ssh-terminfo
# Mouse
mouse-hide-while-typing = true
# Misc
confirm-close-surface = false
# Hyprland/Wayland
gtk-single-instance = true
class = ghostty
# Quick terminal (quake-style dropdown)
quick-terminal-position = top
quick-terminal-animation-duration = 0.1
quick-terminal-autohide = true
keybind = global:ctrl+grave_accent=toggle_quick_terminal

View File

@@ -4,6 +4,9 @@
# Polkit agent (GUI password prompts)
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
# XWayland support
exec-once = xwayland-satellite
# System tray & bar
exec-once = nm-applet &
exec-once = waybar

View File

@@ -102,6 +102,11 @@ windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# Quick Memo popup (yad)
windowrulev2 = float, class:^(yad)$
windowrulev2 = center, class:^(yad)$
windowrulev2 = pin, class:^(yad)$
binds {
hide_special_on_workspace_change = true
workspace_back_and_forth = true
@@ -212,9 +217,12 @@ bind = $mainMod, 9, workspace, 9
# Special workspaces
bind = SUPER, F12, exec, ~/.local/bin/workspace-pin 1337
bind = , F12, togglespecialworkspace, org
bind = SUPER, A, togglespecialworkspace, org
bind = SUPER SHIFT, F12, movetoworkspace, special:org
bind = SUPER SHIFT, A, movetoworkspace, special:org
# Quick Memo (jax-bot integration)
bind = , F12, exec, ~/.local/bin/quick-memo --input
bind = SHIFT, F12, exec, ~/.local/bin/quick-memo --clipboard
bind = SUPER, X, workspace, name:media
bind = SUPER SHIFT, X, movetoworkspace, name:media
bind = SUPER SHIFT, C, movetoworkspace, name:tg