Compare commits
9 commits
57c86c5083
...
5902011dc6
| Author | SHA1 | Date | |
|---|---|---|---|
| 5902011dc6 | |||
| 3fe859e21f | |||
| d6f943ba7f | |||
| 85debcbcd4 | |||
| f49a9c8019 | |||
| 9e49bd830d | |||
| 8feb3957a2 | |||
| 447da21da4 | |||
| a627589a48 |
14 changed files with 233 additions and 53 deletions
|
|
@ -46,6 +46,15 @@ bosun.secrets.npmrc = { ... }; # Secret definitions
|
||||||
|
|
||||||
Profile definitions are in `modules/generic/profiles.nix`, implementations in `modules/nixos/profiles/`.
|
Profile definitions are in `modules/generic/profiles.nix`, implementations in `modules/nixos/profiles/`.
|
||||||
|
|
||||||
|
## Adding packages
|
||||||
|
|
||||||
|
Before writing a custom `packages/` derivation, always check if the package already exists in nixpkgs:
|
||||||
|
- Stable: https://search.nixos.org/packages?channel=25.11&query=<name>
|
||||||
|
- Unstable: https://search.nixos.org/packages?channel=unstable&query=<name>
|
||||||
|
- Master (bleeding edge): check open PRs at https://github.com/NixOS/nixpkgs/pulls
|
||||||
|
|
||||||
|
If it exists in `unstable` or `master` but not stable, pull it via `overlays/unstable.nix` using `channels.unstable` or `channels.master`. Only write a custom `packages/` derivation as a last resort.
|
||||||
|
|
||||||
## Architecture patterns
|
## Architecture patterns
|
||||||
|
|
||||||
- **import-tree** auto-discovers and imports `.nix` files in `modules/flake/`. Files prefixed with `_` are excluded from auto-import.
|
- **import-tree** auto-discovers and imports `.nix` files in `modules/flake/`. Files prefixed with `_` are excluded from auto-import.
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -778,11 +778,11 @@
|
||||||
},
|
},
|
||||||
"master": {
|
"master": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771456066,
|
"lastModified": 1771495899,
|
||||||
"narHash": "sha256-CLuGt3yg70gnhSam+0qpcWgPnUdY98wVeH4lByklol4=",
|
"narHash": "sha256-UlAN9PHsBx1Kk65gR/KvLfO74zQcOjNZ+d/0td5T8eM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "be6cf55e819f4d362aa6be60254bbb2537f9a5cb",
|
"rev": "f4f54061a12ebbdd03d7c53eed54d1e135840624",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@
|
||||||
nodePackages.typescript-language-server
|
nodePackages.typescript-language-server
|
||||||
nil # nix lsp
|
nil # nix lsp
|
||||||
|
|
||||||
|
# Node.js for MCP servers
|
||||||
|
nodejs_22
|
||||||
|
|
||||||
# trurl # Parsing and manipulating URLs via CLI
|
# trurl # Parsing and manipulating URLs via CLI
|
||||||
pandoc # Document converter
|
pandoc # Document converter
|
||||||
ripgrep # Grep file search
|
ripgrep # Grep file search
|
||||||
|
|
@ -39,6 +42,7 @@
|
||||||
gitui
|
gitui
|
||||||
tea
|
tea
|
||||||
harbor.agent-deck # Terminal session manager for AI coding agents
|
harbor.agent-deck # Terminal session manager for AI coding agents
|
||||||
|
happy-coder # Claude Code mobile client (happy.engineering)
|
||||||
harbor.oryx # TUI for sniffing network traffic using eBPF
|
harbor.oryx # TUI for sniffing network traffic using eBPF
|
||||||
|
|
||||||
# BMAD
|
# BMAD
|
||||||
|
|
@ -83,33 +87,48 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
# package = inputs.unstable.${system}.claude-code;
|
# package = inputs.unstable.${system}.claude-code;
|
||||||
|
|
||||||
# mcpServers = {
|
mcpServers = {
|
||||||
# fetch = {
|
# Official MCP servers (require Node.js)
|
||||||
# args = ["-y" "@modelcontextprotocol/server-fetch"];
|
fetch = {
|
||||||
# command = "npx";
|
command = "npx";
|
||||||
# type = "stdio";
|
args = ["-y" "@modelcontextprotocol/server-fetch"];
|
||||||
# };
|
type = "stdio";
|
||||||
# playwright = {
|
};
|
||||||
# args = ["-y" "@modelcontextprotocol/server-playwright"];
|
playwright = {
|
||||||
# command = "npx";
|
command = "npx";
|
||||||
# type = "stdio";
|
args = ["-y" "@modelcontextprotocol/server-playwright"];
|
||||||
# };
|
type = "stdio";
|
||||||
# stackexchange = {
|
};
|
||||||
# args = ["-y" "mcp-server-stackexchange"];
|
stackexchange = {
|
||||||
# command = "npx";
|
command = "npx";
|
||||||
# type = "stdio";
|
args = ["-y" "mcp-server-stackexchange"];
|
||||||
# };
|
type = "stdio";
|
||||||
# arxiv = {
|
};
|
||||||
# args = ["-y" "mcp-server-arxiv"];
|
arxiv = {
|
||||||
# command = "npx";
|
command = "npx";
|
||||||
# type = "stdio";
|
args = ["-y" "mcp-server-arxiv"];
|
||||||
# };
|
type = "stdio";
|
||||||
# claudezilla = {
|
};
|
||||||
# command = "bun";
|
|
||||||
# args = ["/home/toph/code/vendor/claudezilla/mcp/server.js"];
|
# Custom MCP servers
|
||||||
# type = "stdio";
|
claudezilla = {
|
||||||
# };
|
command = "bun";
|
||||||
# };
|
args = ["/home/toph/code/vendor/claudezilla/mcp/server.js"];
|
||||||
|
type = "stdio";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Charlie MCP servers (local)
|
||||||
|
charlie-memory = {
|
||||||
|
command = "bun";
|
||||||
|
args = ["/home/toph/agent/mcp/memory/index.js"];
|
||||||
|
type = "stdio";
|
||||||
|
};
|
||||||
|
charlie-comunica = {
|
||||||
|
command = "bun";
|
||||||
|
args = ["/home/toph/agent/mcp/comunica/index.js"];
|
||||||
|
type = "stdio";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
enabled = true;
|
enabled = true;
|
||||||
size = 4;
|
size = 4;
|
||||||
passes = 2;
|
passes = 2;
|
||||||
noise = 0.3;
|
noise = 0.05;
|
||||||
};
|
};
|
||||||
shadow = {
|
shadow = {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
|
|
@ -69,6 +69,16 @@
|
||||||
|
|
||||||
# Single tiled window on main monitor: give it breathing room
|
# Single tiled window on main monitor: give it breathing room
|
||||||
workspace = [
|
workspace = [
|
||||||
|
# Pin workspaces to monitors
|
||||||
|
"1, monitor:HDMI-A-1, default:true"
|
||||||
|
"2, monitor:HDMI-A-1"
|
||||||
|
"3, monitor:HDMI-A-1"
|
||||||
|
"4, monitor:HDMI-A-1"
|
||||||
|
"5, monitor:HDMI-A-1"
|
||||||
|
"6, monitor:DP-3, default:true"
|
||||||
|
"7, monitor:DP-3"
|
||||||
|
"8, monitor:DP-3"
|
||||||
|
# Single tiled window on main monitor: give it breathing room
|
||||||
"w[t1] m[HDMI-A-1], gapsout:15 840 15 840"
|
"w[t1] m[HDMI-A-1], gapsout:15 840 15 840"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{...}: {
|
{...}: {
|
||||||
wayland.windowManager.hyprland.settings = {
|
wayland.windowManager.hyprland.settings = {
|
||||||
windowrulev2 = [
|
windowrulev2 = [
|
||||||
|
# Prevent garbage frame flash before first render
|
||||||
|
"immediate, class:.*"
|
||||||
|
|
||||||
# Privacy: block from screen capture
|
# Privacy: block from screen capture
|
||||||
"noscreenshare, class:^(1password)$"
|
"noscreenshare, class:^(1password)$"
|
||||||
"noscreenshare, class:^(thunderbird)$"
|
"noscreenshare, class:^(thunderbird)$"
|
||||||
|
|
@ -21,8 +24,9 @@
|
||||||
# Kitty: slight transparency
|
# Kitty: slight transparency
|
||||||
"opacity 0.97 0.97, class:^(kitty)$"
|
"opacity 0.97 0.97, class:^(kitty)$"
|
||||||
|
|
||||||
# JetBrains: suppress phantom windows
|
# JetBrains: suppress phantom windows (RE2 doesn't support lookaheads,
|
||||||
"nofocus, class:^jetbrains-(?!toolbox), floating:1, title:^win\\d+$"
|
# so we can't exclude toolbox here — apply per-IDE if needed)
|
||||||
|
# "nofocus, class:^(jetbrains-idea|jetbrains-rider|jetbrains-clion)$, floating:1, title:^win\\d+$"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
inherit lib;
|
inherit lib;
|
||||||
pkgs = prev;
|
pkgs = prev;
|
||||||
};
|
};
|
||||||
|
harbor = inputs.self.packages.${system} or {};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
# open-webui
|
# open-webui
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit (channels.master) install-nothing marimo;
|
inherit (channels.master) install-nothing marimo happy-coder;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
116
packages/active-path/active-path
Normal file
116
packages/active-path/active-path
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# active-path <pid>
|
||||||
|
#
|
||||||
|
# Given a PID (e.g. from active-window), output the active working directory
|
||||||
|
# or project root for that process. Handles:
|
||||||
|
# - VSCode/Electron: parses --folder-uri from the process tree
|
||||||
|
# - Terminals (kitty, etc.): finds the deepest meaningful cwd in the tree
|
||||||
|
# - Fallback: own cwd
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
PID="${1:?Usage: active-path <pid>}"
|
||||||
|
|
||||||
|
PROJECT_MARKERS=(.git Cargo.toml package.json Gemfile pyproject.toml go.mod flake.nix)
|
||||||
|
|
||||||
|
# Walk up from a path looking for a project root marker.
|
||||||
|
# Returns the marker directory, or the original path if nothing is found.
|
||||||
|
find_project_root() {
|
||||||
|
local dir="$1"
|
||||||
|
local d="$dir"
|
||||||
|
while [[ "$d" != "/" && -n "$d" ]]; do
|
||||||
|
for marker in "${PROJECT_MARKERS[@]}"; do
|
||||||
|
[[ -e "$d/$marker" ]] && { printf '%s\n' "$d"; return 0; }
|
||||||
|
done
|
||||||
|
d="$(dirname "$d")"
|
||||||
|
done
|
||||||
|
printf '%s\n' "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Decode a percent-encoded URI path component (file:/// paths are rarely
|
||||||
|
# exotic, but spaces and non-ASCII chars do occur).
|
||||||
|
url_decode() {
|
||||||
|
printf '%b' "${1//%/\\x}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read null-terminated argv of a pid and look for --folder-uri.
|
||||||
|
# Prints the decoded path and returns 0 if found, else returns 1.
|
||||||
|
folder_uri_from_cmdline() {
|
||||||
|
local pid="$1"
|
||||||
|
local prev=""
|
||||||
|
while IFS= read -r -d $'\0' arg; do
|
||||||
|
case "$arg" in
|
||||||
|
--folder-uri=file://*)
|
||||||
|
url_decode "${arg#--folder-uri=file://}"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
file://*)
|
||||||
|
if [[ "$prev" == "--folder-uri" ]]; then
|
||||||
|
url_decode "${arg#file://}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
prev="$arg"
|
||||||
|
done < "/proc/$pid/cmdline" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect PIDs of all processes in the subtree rooted at $1.
|
||||||
|
all_pids() {
|
||||||
|
local pid="$1"
|
||||||
|
printf '%s\n' "$pid"
|
||||||
|
for child in $(pgrep -P "$pid" 2>/dev/null); do
|
||||||
|
all_pids "$child"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Given a list of PIDs, return the most specific (longest) cwd that lives
|
||||||
|
# under the user's home directory. Falls back to any non-root, non-proc cwd.
|
||||||
|
best_cwd_from_pids() {
|
||||||
|
local best=""
|
||||||
|
for pid in "$@"; do
|
||||||
|
local cwd
|
||||||
|
cwd=$(readlink -f "/proc/$pid/cwd" 2>/dev/null) || continue
|
||||||
|
[[ "$cwd" == "/" || "$cwd" == "/root" || "$cwd" == "${HOME}" ]] && continue
|
||||||
|
[[ "$cwd" =~ ^/(proc|sys|run|dev) ]] && continue
|
||||||
|
|
||||||
|
# Prefer paths under home; among those prefer longer (more specific) ones
|
||||||
|
if [[ "$cwd" == "${HOME}"/* ]]; then
|
||||||
|
if [[ -z "$best" || "${#cwd}" -gt "${#best}" ]]; then
|
||||||
|
best="$cwd"
|
||||||
|
fi
|
||||||
|
elif [[ -z "$best" ]]; then
|
||||||
|
best="$cwd"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[[ -n "$best" ]] && printf '%s\n' "$best"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main ---
|
||||||
|
|
||||||
|
readarray -t TREE < <(all_pids "$PID")
|
||||||
|
|
||||||
|
# 1. VSCode / Electron: search entire process tree for --folder-uri
|
||||||
|
for pid in "${TREE[@]}"; do
|
||||||
|
if path=$(folder_uri_from_cmdline "$pid" 2>/dev/null); then
|
||||||
|
find_project_root "$path"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. Terminal / generic: find the best cwd among all processes in the tree
|
||||||
|
# (prefers the shell or its running child over the terminal emulator itself,
|
||||||
|
# because terminal emulators typically sit at $HOME while the shell has moved)
|
||||||
|
if cwd=$(best_cwd_from_pids "${TREE[@]}"); then
|
||||||
|
find_project_root "$cwd"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Absolute fallback: own cwd, even if it's /
|
||||||
|
own=$(readlink -f "/proc/$PID/cwd" 2>/dev/null || true)
|
||||||
|
[[ -n "$own" ]] && find_project_root "$own" && exit 0
|
||||||
|
|
||||||
|
exit 1
|
||||||
6
packages/active-path/package.nix
Normal file
6
packages/active-path/package.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{pkgs, ...}:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "active-path";
|
||||||
|
runtimeInputs = [pkgs.procps];
|
||||||
|
text = builtins.readFile ./active-path;
|
||||||
|
}
|
||||||
19
packages/active-window/package.nix
Normal file
19
packages/active-window/package.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{pkgs, ...}:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "active-window";
|
||||||
|
runtimeInputs = [pkgs.jq];
|
||||||
|
text = ''
|
||||||
|
if [[ -n "''${NIRI_SOCKET:-}" ]]; then
|
||||||
|
FOCUSED_ID=$(niri msg --json focused-window | jq '.id')
|
||||||
|
niri msg --json windows \
|
||||||
|
| jq --argjson id "$FOCUSED_ID" \
|
||||||
|
'[.[] | select(.id == $id)] | first
|
||||||
|
| {compositor: "niri", class: .app_id, pid: .pid}'
|
||||||
|
elif [[ -n "''${HYPRLAND_INSTANCE_SIGNATURE:-}" ]]; then
|
||||||
|
hyprctl activewindow -j \
|
||||||
|
| jq '{compositor: "hyprland", class: .class, pid: .pid}'
|
||||||
|
else
|
||||||
|
printf '{"compositor":"unknown","class":null,"pid":null}\n'
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
}
|
||||||
6
packages/mcp-wrapper/default.nix
Normal file
6
packages/mcp-wrapper/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
pkgs.writeShellScriptBin "mcp-npx" ''
|
||||||
|
# Wrapper that provides npx with Node.js in PATH
|
||||||
|
exec ${pkgs.nodejs_22}/bin/npx "$@"
|
||||||
|
''
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{pkgs, ...}:
|
{pkgs, ...}:
|
||||||
pkgs.writeShellApplication {
|
pkgs.writeShellApplication {
|
||||||
name = "quick-zeal";
|
name = "quick-zeal";
|
||||||
|
runtimeInputs = [pkgs.harbor.active-window pkgs.jq];
|
||||||
text = builtins.readFile ./quick-zeal;
|
text = builtins.readFile ./quick-zeal;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ extract_major_version() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Detects the focused window and checks if it's Kitty
|
# Detects the focused window and checks if it's Kitty
|
||||||
ACTIVE_WINDOW=$(hyprctl activewindow -j | jq -r '.class')
|
WINDOW=$(active-window)
|
||||||
|
ACTIVE_WINDOW=$(printf '%s' "$WINDOW" | jq -r '.class // empty')
|
||||||
|
PID=$(printf '%s' "$WINDOW" | jq -r '.pid // empty')
|
||||||
|
|
||||||
# Check if the focused window is a Kitty terminal and if it's in a Git repository.
|
# Check if the focused window is a Kitty terminal and if it's in a Git repository.
|
||||||
# If so, determine the project type and open Zeal with the appropriate argument
|
# If so, determine the project type and open Zeal with the appropriate argument
|
||||||
zeal_argument=""
|
zeal_argument=""
|
||||||
if [[ $ACTIVE_WINDOW == "kitty" ]]; then
|
if [[ $ACTIVE_WINDOW == "kitty" ]]; then
|
||||||
PID=$(hyprctl activewindow -j | jq -r '.pid')
|
|
||||||
CHILD_PID=$(pgrep -P "$PID" | tail -1)
|
CHILD_PID=$(pgrep -P "$PID" | tail -1)
|
||||||
|
|
||||||
SHELL_CWD=$(readlink -f "/proc/${CHILD_PID}/cwd")
|
SHELL_CWD=$(readlink -f "/proc/${CHILD_PID}/cwd")
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,13 @@
|
||||||
{pkgs, ...}:
|
{pkgs, ...}:
|
||||||
pkgs.writeNushellApplication {
|
pkgs.writeNushellApplication {
|
||||||
name = "spawn-term";
|
name = "spawn-term";
|
||||||
runtimeInputs = with pkgs; [kdotool];
|
runtimeInputs = [pkgs.harbor.active-window];
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
let compositor = $env.XDG_CURRENT_DESKTOP? | default ""
|
let window = (active-window | from json)
|
||||||
|
let window_info = {
|
||||||
let window_info = if ($compositor | str contains "niri") {
|
is_kitty: ($window.class? == "kitty"),
|
||||||
let focused_window = (niri msg --json focused-window | from json | get id?)
|
pid: $window.pid?
|
||||||
if ($focused_window | is-empty) {
|
|
||||||
{ is_kitty: false, pid: null }
|
|
||||||
} else {
|
|
||||||
let info = (niri msg --json windows | from json | where id == $focused_window | first)
|
|
||||||
{ is_kitty: ($info.app_id? == "kitty"), pid: $info.pid? }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let focused_window = (kdotool getactivewindow)
|
|
||||||
{
|
|
||||||
is_kitty: ((kdotool getwindowclassname $focused_window) == "kitty"),
|
|
||||||
pid: (kdotool getwindowpid $focused_window | into int)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if $window_info.is_kitty {
|
if $window_info.is_kitty {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue