Compare commits

..

14 commits

Author SHA1 Message Date
5902011dc6
feat: add mcp-npx wrapper for Node.js MCP servers
Provides npx with Node.js in PATH for MCP server execution.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:26:28 +01:00
3fe859e21f
feat(hyprland): add immediate render rule to prevent frame flash
Applies immediate rendering to all windows to eliminate the garbage
frame flash that appears before first render.

Also documents JetBrains phantom window suppression limitation (RE2
doesn't support lookaheads, so can't exclude toolbox).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:26:20 +01:00
d6f943ba7f
feat(hyprland): pin workspaces to monitors and reduce blur noise
- Pins workspaces 1-5 to HDMI-A-1 (main ultrawide)
- Pins workspaces 6-8 to DP-3 (vertical display)
- Reduces blur noise from 0.3 to 0.05 for cleaner appearance

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:26:05 +01:00
85debcbcd4
feat: enable MCP servers and add happy-coder
- Enables official MCP servers (fetch, playwright, stackexchange, arxiv)
- Adds Charlie MCP servers (memory, comunica)
- Adds claudezilla MCP server
- Adds happy-coder mobile client from nixpkgs master
- Adds nodejs_22 for MCP server runtime

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:53 +01:00
f49a9c8019
refactor: use active-window utility in scripts
Migrates quick-zeal and spawn-term from compositor-specific APIs
(hyprctl, kdotool, niri msg) to the unified active-window utility.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:46 +01:00
9e49bd830d
feat: add compositor-agnostic window info utilities
- active-window: Get focused window info (class, pid) for Niri/Hyprland
- active-path: Get CWD of focused terminal window

These utilities abstract compositor-specific APIs for use in scripts.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:45 +01:00
8feb3957a2
fix: expose harbor package namespace to overlays
Adds harbor (self.packages) to overlay arguments, allowing custom
packages to reference each other via pkgs.harbor.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:36 +01:00
447da21da4
docs: add package search guidelines to CLAUDE.md
Documents the nixpkgs search workflow before writing custom packages.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:33 +01:00
a627589a48
chore: update flake inputs
Updates nixpkgs master channel.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:25:30 +01:00
57c86c5083
feat(hyprland): add workspace 6-8 keybinds
Adds missing workspace keybinds to match monitor setup:
- Focus workspaces 6-8
- Move windows to workspaces 6-8 silently

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:20:12 +01:00
ceb5ee4356
feat: add cliphist clipboard history manager with Mod+v keybind
Enables clipboard history for both Niri and Hyprland compositors:
- Added wl-clipboard and cliphist packages to Niri
- Configured clipboard watchers for text and images in Niri autostart
- Bound Mod+v to open clipboard history via fuzzel in both compositors

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 01:19:27 +01:00
ae2ee1fb77
feat: migrate from S3 binary cache to Attic
Replace the S3-based Nix binary cache with Attic, a multi-tenant
binary cache server with better deduplication and garbage collection.

Changes:
- Install attic-client system-wide from nixpkgs
- Update substituter URL from s3.toph.so to cache.toph.so/toph
- Replace S3 cache keys with Attic cache signing key
- Add attic client config for endurance host

The Attic cache provides the same functionality as the S3 cache but
with improved performance and multi-tenancy support.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-27 11:14:43 +01:00
7af406f315
feat: add Hyprland as an additional compositor alongside Niri
Enables programs.hyprland at the system level, adds xdg-desktop-portal-hyprland
with per-compositor portal routing, and wires up a home-manager config for
endurance mirroring the Niri layout (same keybinds, monitors, window rules).
Niri remains the default SDDM session.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-19 10:53:30 +01:00
c694915cd5
feat: add cache.toph.so Nix binary cache key and substituter 2026-02-18 13:50:47 +01:00
34 changed files with 549 additions and 55 deletions

View file

@ -46,6 +46,15 @@ bosun.secrets.npmrc = { ... }; # Secret definitions
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
- **import-tree** auto-discovers and imports `.nix` files in `modules/flake/`. Files prefixed with `_` are excluded from auto-import.

6
flake.lock generated
View file

@ -778,11 +778,11 @@
},
"master": {
"locked": {
"lastModified": 1767143992,
"narHash": "sha256-c3jlq36uxltxGLuQ3KPYfxZkue/LLD0Ct3NdhBUsRyo=",
"lastModified": 1771495899,
"narHash": "sha256-UlAN9PHsBx1Kk65gR/KvLfO74zQcOjNZ+d/0td5T8eM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5830d8dfe6ae79365987d78bda3dd4152c271d8b",
"rev": "f4f54061a12ebbdd03d7c53eed54d1e135840624",
"type": "github"
},
"original": {

View file

@ -0,0 +1,7 @@
{...}: {
# Attic binary cache client configuration
xdg.configFile."attic/config.toml".text = ''
[cache.toph]
endpoint = "https://cache.toph.so"
'';
}

View file

@ -0,0 +1,32 @@
{ pkgs, config, lib, ... }: {
home.packages = [ pkgs.awscli2 ];
# Derive AWS credentials from the existing nix-cache S3 secret — same
# credentials, different format. No duplication, no Nomad API access needed.
age.generators.aws-credentials = { decrypt, deps, ... }: ''
KEY=$(${decrypt} ${lib.escapeShellArg deps.nix-cache-s3-env.file} \
| grep AWS_ACCESS_KEY_ID | cut -d= -f2-)
SECRET=$(${decrypt} ${lib.escapeShellArg deps.nix-cache-s3-env.file} \
| grep AWS_SECRET_ACCESS_KEY | cut -d= -f2-)
printf '[t4]\naws_access_key_id = %s\naws_secret_access_key = %s\n' \
"$KEY" "$SECRET"
'';
bosun.secrets.aws-credentials = {
rekeyFile = "aws-credentials.age";
path = "${config.home.homeDirectory}/.aws/credentials";
mode = "0600";
generator = {
script = "aws-credentials";
dependencies = {
inherit (config.age.secrets) nix-cache-s3-env;
};
};
};
home.file.".aws/config".text = ''
[profile t4]
endpoint_url = https://s3.toph.so
region = us-east-1
'';
}

View file

@ -15,6 +15,7 @@
rfc # TUI-based RFC reader
nix-init # Generate Nix packages from URLs
install-nothing
marimo
# Language Servers
lua-language-server
@ -23,6 +24,9 @@
nodePackages.typescript-language-server
nil # nix lsp
# Node.js for MCP servers
nodejs_22
# trurl # Parsing and manipulating URLs via CLI
pandoc # Document converter
ripgrep # Grep file search
@ -38,6 +42,7 @@
gitui
tea
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
# BMAD
@ -82,33 +87,48 @@
enable = true;
# package = inputs.unstable.${system}.claude-code;
# mcpServers = {
# fetch = {
# args = ["-y" "@modelcontextprotocol/server-fetch"];
# command = "npx";
# type = "stdio";
# };
# playwright = {
# args = ["-y" "@modelcontextprotocol/server-playwright"];
# command = "npx";
# type = "stdio";
# };
# stackexchange = {
# args = ["-y" "mcp-server-stackexchange"];
# command = "npx";
# type = "stdio";
# };
# arxiv = {
# args = ["-y" "mcp-server-arxiv"];
# command = "npx";
# type = "stdio";
# };
# claudezilla = {
# command = "bun";
# args = ["/home/toph/code/vendor/claudezilla/mcp/server.js"];
# type = "stdio";
# };
# };
mcpServers = {
# Official MCP servers (require Node.js)
fetch = {
command = "npx";
args = ["-y" "@modelcontextprotocol/server-fetch"];
type = "stdio";
};
playwright = {
command = "npx";
args = ["-y" "@modelcontextprotocol/server-playwright"];
type = "stdio";
};
stackexchange = {
command = "npx";
args = ["-y" "mcp-server-stackexchange"];
type = "stdio";
};
arxiv = {
command = "npx";
args = ["-y" "mcp-server-arxiv"];
type = "stdio";
};
# Custom MCP servers
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";
};
};
};
};
}

View file

@ -15,6 +15,7 @@
./wakatime.nix
./gpg
./niri
./hyprland
./stylix.nix
./default-applications.nix
./misc/launcher.nix

View file

@ -0,0 +1,15 @@
{pkgs, ...}: {
imports = [
./settings.nix
./keybinds.nix
./window-rules.nix
];
wayland.windowManager.hyprland.enable = true;
home.packages = with pkgs; [
wl-clipboard
cliphist
hyprpicker
];
}

View file

@ -0,0 +1,66 @@
{pkgs, ...}: {
wayland.windowManager.hyprland.settings = {
"$mod" = "SUPER";
bind = [
# Apps
"$mod, space, exec, fuzzel"
"$mod, c, exec, ${pkgs.harbor.spawn-term}/bin/spawn-term"
"$mod, e, exec, nautilus"
"$mod, d, exec, zeal"
"$mod, g, exec, ${pkgs.harbor.hg-picker}/bin/hg-picker"
"$mod, v, exec, cliphist list | fuzzel --dmenu | cliphist decode | wl-copy"
# Window management
"$mod, q, killactive"
"$mod, f, fullscreen, 1"
"$mod, t, togglefloating"
"$mod, p, pseudo"
"$mod SHIFT, j, togglesplit"
# Focus (vim-style, matching niri keybinds)
"$mod, h, movefocus, l"
"$mod, l, movefocus, r"
"$mod, k, movefocus, u"
"$mod, j, movefocus, d"
# Move windows
"$mod CTRL, h, movewindow, l"
"$mod CTRL, l, movewindow, r"
"$mod CTRL, k, movewindow, u"
"$mod CTRL, j, movewindow, d"
# Workspaces
"$mod, 1, workspace, 1"
"$mod, 2, workspace, 2"
"$mod, 3, workspace, 3"
"$mod, 4, workspace, 4"
"$mod, 5, workspace, 5"
"$mod, 6, workspace, 6"
"$mod, 7, workspace, 7"
"$mod, 8, workspace, 8"
# Move to workspace silently
"$mod CTRL, 1, movetoworkspacesilent, 1"
"$mod CTRL, 2, movetoworkspacesilent, 2"
"$mod CTRL, 3, movetoworkspacesilent, 3"
"$mod CTRL, 4, movetoworkspacesilent, 4"
"$mod CTRL, 5, movetoworkspacesilent, 5"
"$mod CTRL, 6, movetoworkspacesilent, 6"
"$mod CTRL, 7, movetoworkspacesilent, 7"
"$mod CTRL, 8, movetoworkspacesilent, 8"
];
bindm = [
# Mouse: move and resize windows
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
];
bindl = [
# Scroll through workspaces
"$mod, mouse_down, workspace, e+1"
"$mod, mouse_up, workspace, e-1"
];
};
}

View file

@ -0,0 +1,95 @@
{pkgs, ...}: {
wayland.windowManager.hyprland.settings = {
monitor = [
# Main ultrawide (left)
"HDMI-A-1,3840x1080,0x0,1"
# Vertical display (right)
"DP-3,1920x1080,3840x-430,1,transform,1"
# Fallback for any other monitor
",preferred,auto,1"
];
exec-once = [
# Clipboard history
"wl-paste --type text --watch cliphist store"
"wl-paste --type image --watch cliphist store"
# Automount USB drives
"udiskie"
];
env = [
"MOZ_ENABLE_WAYLAND,1"
"QT_WAYLAND_DISABLE_WINDOWDECORATION,1"
"ELECTRON_OZONE_PLATFORM_HINT,auto"
"NIXOS_OZONE_WL,1"
"XDG_SESSION_TYPE,wayland"
"XDG_CURRENT_DESKTOP,Hyprland"
];
input = {
kb_layout = "us";
kb_variant = "intl";
follow_mouse = 2;
sensitivity = 0;
};
general = {
gaps_in = 8;
gaps_out = 15;
border_size = 2;
layout = "dwindle";
};
decoration = {
rounding = 8;
blur = {
enabled = true;
size = 4;
passes = 2;
noise = 0.05;
};
shadow = {
enabled = true;
range = 4;
render_power = 3;
};
};
animations = {
enabled = true;
bezier = "myBezier, 0.05, 0.9, 0.1, 1.05";
animation = [
"windows, 1, 7, myBezier"
"windowsOut, 1, 7, default, popin 80%"
"border, 1, 10, default"
"fade, 1, 7, default"
"workspaces, 1, 6, default"
];
};
# Single tiled window on main monitor: give it breathing room
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"
];
dwindle = {
pseudotile = true;
preserve_split = true;
};
misc = {
disable_hyprland_logo = true;
disable_splash_rendering = true;
};
};
}

View file

@ -0,0 +1,32 @@
{...}: {
wayland.windowManager.hyprland.settings = {
windowrulev2 = [
# Prevent garbage frame flash before first render
"immediate, class:.*"
# Privacy: block from screen capture
"noscreenshare, class:^(1password)$"
"noscreenshare, class:^(thunderbird)$"
"noscreenshare, title:^(ld\\.toph\\.so)$"
# Floating windows
"float, class:^(org\\.zealdocs\\.zeal)$"
"center 1, class:^(org\\.zealdocs\\.zeal)$"
"size 50% 80%, class:^(org\\.zealdocs\\.zeal)$"
"minsize 1400 500, class:^(org\\.zealdocs\\.zeal)$"
"float, class:^(speedcrunch)$"
"center 1, class:^(speedcrunch)$"
"size 30% 60%, class:^(speedcrunch)$"
"float, title:^(DevTools)$"
# Kitty: slight transparency
"opacity 0.97 0.97, class:^(kitty)$"
# JetBrains: suppress phantom windows (RE2 doesn't support lookaheads,
# 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+$"
];
};
}

View file

@ -2,5 +2,9 @@
programs.niri.settings.spawn-at-startup = [
# open fastfetch by default
{argv = ["kitty" "--title" "'fastfetch'" "sh" "-c" "'fastfetch; read'"];}
# Clipboard history daemons
{argv = ["wl-paste" "--type" "text" "--watch" "cliphist" "store"];}
{argv = ["wl-paste" "--type" "image" "--watch" "cliphist" "store"];}
];
}

View file

@ -15,5 +15,7 @@
home.packages = with pkgs; [
fuzzel
wl-clipboard
cliphist
];
}

View file

@ -12,6 +12,7 @@
"Mod+d".action = spawn "zeal"; # Documentation viewer
"Mod+c".action = spawn "${pkgs.harbor.spawn-term}/bin/spawn-term";
"Mod+g".action = spawn "${pkgs.harbor.hg-picker}/bin/hg-picker";
"Mod+v".action = spawn "sh" "-c" "cliphist list | fuzzel --dmenu | cliphist decode | wl-copy";
"Mod+q".action = close-window;
"Mod+f".action = fullscreen-window;

View file

@ -20,6 +20,25 @@
#quirks = ["avahi" "docker" "nix-ld"];
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPjqieS4GkYAa1WRYZpxjgYsj7VGZ9U+rTFCkX8M0umD";
secrets = {
nix-cache-privkey = "nix-cache-privkey.age";
nix-cache-s3-env = "nix-cache-s3.env.age";
};
};
# Sign builds and push to the S3 binary cache at s3.toph.so/nix-cache.
nix.settings = {
secret-key-files = [config.age.secrets.nix-cache-privkey.path];
post-build-hook = toString (pkgs.writeScript "nix-cache-upload" ''
#!/bin/sh
set -uf
. ${config.age.secrets.nix-cache-s3-env.path}
${config.nix.package}/bin/nix copy \
--to "s3://nix-cache?endpoint=https://s3.toph.so&region=us-east-1" \
$OUT_PATHS \
>> /tmp/nix-cache-upload.log 2>&1 &
'');
};
networking = {

View file

@ -24,6 +24,7 @@
inherit lib;
pkgs = prev;
};
harbor = inputs.self.packages.${system} or {};
})
];
};

View file

@ -24,5 +24,6 @@
just
nh
age
attic-client
];
}

View file

@ -30,7 +30,14 @@
keep-outputs = true;
trusted-users = ["root" "@wheel"];
substituters = ["https://cache.nixos.org/"];
substituters = [
"https://cache.nixos.org/"
"https://cache.toph.so/toph"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"toph:E/oP7KyljH/yprI5LArxNPpSlQCdo29sMOkh3jm53Yg="
];
};
};

View file

@ -20,11 +20,13 @@ in {
'';
functions = {
nomad-ui = {
description = "Fetches the Nomad management token from alvin and opens the authenticated UI";
nomad-auth = {
description = "Fetches the Nomad management token from alvin. Pass --ui to also open the authenticated UI.";
body = ''
set -gx NOMAD_TOKEN (ssh root@alvin cat /var/lib/nomad-acl/management.token)
nomad ui -authenticate
if contains -- --ui $argv
nomad ui -authenticate
end
'';
};
agx = {

View file

@ -32,6 +32,8 @@ in {
package = pkgs.niri; # TODO: Use input niri pkgs/overlay!
};
programs.hyprland.enable = true;
services = {
xserver = {
enable = true;
@ -62,11 +64,15 @@ in {
xdg.portal = {
enable = true;
# xdgOpenUsePortal = true;
config.common.default = "gtk";
config = {
common.default = "gtk";
hyprland.default = ["hyprland" "gtk"];
};
extraPortals = with pkgs; [
xdg-desktop-portal-gnome
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
];
};

BIN
new-secret-ci.key Normal file

Binary file not shown.

View file

@ -17,5 +17,5 @@
# open-webui
;
inherit (channels.master) install-nothing;
inherit (channels.master) install-nothing marimo happy-coder;
}

View 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

View file

@ -0,0 +1,6 @@
{pkgs, ...}:
pkgs.writeShellApplication {
name = "active-path";
runtimeInputs = [pkgs.procps];
text = builtins.readFile ./active-path;
}

View 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
'';
}

View 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 "$@"
''

View file

@ -1,5 +1,6 @@
{pkgs, ...}:
pkgs.writeShellApplication {
name = "quick-zeal";
runtimeInputs = [pkgs.harbor.active-window pkgs.jq];
text = builtins.readFile ./quick-zeal;
}

View file

@ -6,13 +6,14 @@ extract_major_version() {
}
# 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.
# If so, determine the project type and open Zeal with the appropriate argument
zeal_argument=""
if [[ $ACTIVE_WINDOW == "kitty" ]]; then
PID=$(hyprctl activewindow -j | jq -r '.pid')
CHILD_PID=$(pgrep -P "$PID" | tail -1)
SHELL_CWD=$(readlink -f "/proc/${CHILD_PID}/cwd")

View file

@ -1,25 +1,13 @@
{pkgs, ...}:
pkgs.writeNushellApplication {
name = "spawn-term";
runtimeInputs = with pkgs; [kdotool];
runtimeInputs = [pkgs.harbor.active-window];
text = ''
let compositor = $env.XDG_CURRENT_DESKTOP? | default ""
let window_info = if ($compositor | str contains "niri") {
let focused_window = (niri msg --json focused-window | from json | get id?)
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)
}
let window = (active-window | from json)
let window_info = {
is_kitty: ($window.class? == "kitty"),
pid: $window.pid?
}
if $window_info.is_kitty {

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> X25519 j9CFK6quTBAviKfxt7nk/sdsTR/5swqEfqLgo7gjmTw
BSrgJCxZY1nIOtjkzAdx60POzJ98IF5ryF1SLXKMgZk
-> piv-p256 Kmn3OQ AwqKwTZh40d7YdbU3mMJFhKz75X/NKfXdBCzpKWf75uo
FIuAwxkVMN12HoV7SN7iq1fOhqakL4Lbz5Wp/PabTO4
-> F=1e<+J-grease pk rVDW+r/ Zme4
y52QMNBhnOd9wwF4NauKUGkRCt99O7L+cqGD6od03cDf79bfsCxc0jyY7wW+fe9R
TgFDwzdeRl4LQCL/3uH4bj+j
--- zuMUxvMUyLES8iVG6tbdW/oU0bAaTDlRJwN7x+k6kTw
Ê_Ï5ýZÄB£öÛù~Úi©Ç%<25>B¸3RÕËrÜËôê˜<C3AA>W<| ASƒ•=œ×³Û*IÁ~޻ئ<C398>y$¦äY´`Óp|F¨Èz?nR ÄÂâÚ4h7y·Á# /r…Vy«|Ò¢B+~òBfÁƒ;±å|áÇWu¤$Y“E=ËÇIÁU'… ¬
¯

Binary file not shown.

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> X25519 pxQqMT3kixRevDw5AOugW61Stxpa54NPwdaMDf5ywBc
emM75EuTf5bL1eueeWsmFfbEZDT5+27VzBOQgCfGzlw
-> piv-p256 Kmn3OQ A92Zkli15jy0zZZiW+VYVi5apkhwXi5gZdGsfz4Nzrai
wP2maNvDOmxxA6PMcj930SJLFnExpQHYOqWVXvh2g3o
-> ,*$Gmjb-grease `Ob>AA UG\`RJP S @-2
+HbUwSV+W2xrg8coxxpvSQ+VfPGWfqr0HjtDmX3wCmCO0jpjqKqRCDEAsTj5oTKL
MUOwTg
--- Ib2BGSJRp6WTD55zNN072D+RFxRegKZhjXa48sKg3+Q
ìLŸôÐô~\FþQ$€´¬šáûÌFõ²
 E±EØ®ÔMi[x¿/z ÒþŠ¢·ÿÃ×s‡ÆüÆg®l8· 6tÒìi]ø¨0Èo6—¿%ÆúáÜÐc/ïÃ+%Ô¹Œ~ùšHˆpPŽ‘%uQ;шŒ_[p_§²}ªw†È

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 /u/eYA jAj/U8H5mR2k4JV8FEckBb8vmpGP9LLCFCFATVZd4yA
WyXJPAyZS2Dq93pK2GuVYNbBIxNyvEVJuVWrpZS89Zs
-> @W-grease _
Gd35VA
--- 6blY/MMnEQunu/tWdJv/oGaqf1i6Pv+xdrqLV0EZNes
«<EFBFBD>ˆëÇægŒúóþ/½çM0•2äPçÿ!°9XUÂxÖKï¦(ònÊ“ëçã×…/¯j„[`fÃ<66>.Nf¯f1ã ÃVíŒë€á!·rÆ}h`êûåÑeƒàsÀ/&8¾£þÅÛ¥«ûSÙ2ËÏ!o£úõ¾NjÞÕpäL?ïX]Ív<C38D>8C

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 Sih9FA DiICdXQXdFzjmLaO3TbZmurGr7rRlOyeyT4B6Q2hbgw
AMltu9LuCxoev0zm5Ihoa0aSYVvs7SAD04NIF4gyMxw
-> K0!Lf-grease AnUy
q4vQ1RJdAeh03A
--- g4sTWmo/FkvfmPBcfalOQE3FUapLvqYKLEfqDUvt2Yw
*ñUäA†±=¾w1üƒ·&º*d©UÙÎ<C399>s>a4¦s«˜hi<68>g<EFBFBD>€}K Š»}ÒïàÆ8dÄþp‰¸œˆÔVë†bB¼•†TxéÊ<C3A9>ÈŒ0sˆüdÃ÷Q¿UR+
nF?æÈÚ ýƒÏP'i¥Xœ²)¥UsÜq¶²EŒ~Êèi¥ûÀˆ=âµ|<7C>P.¥ùÁâ2