Compare commits
244
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a8107ea90 | ||
|
|
4935fcb9ee | ||
|
|
a2f08671a6 | ||
|
|
b708b2a05f | ||
|
|
41fbe75abc | ||
|
|
778192e5e6 | ||
|
|
1f149155b4 | ||
|
|
a2cb2b6319 | ||
|
|
6ff6deb4e3 | ||
|
|
5d9fe6afb7 | ||
|
|
b2208277c4 | ||
|
|
9adfa185bb | ||
|
|
a1b6950e93 | ||
|
|
25a44e79fa | ||
|
|
c615eb5c1e | ||
|
|
aa084be01a | ||
|
|
3794500230 | ||
|
|
0867492170 | ||
|
|
cab1f73c89 | ||
|
|
613e9223e4 | ||
|
|
03ffb69526 | ||
|
|
7d231f0a05 | ||
|
|
b6f13f06d0 | ||
|
|
72f4784445 | ||
|
|
3d8f9e3003 | ||
|
|
6426490fe7 | ||
|
|
8dfe6a8356 | ||
|
|
1a9a60b0db | ||
|
|
77833a7eca | ||
|
|
715d06e83b | ||
|
|
19332ebb74 | ||
|
|
b65bbcacbe | ||
|
|
feb5dcd673 | ||
|
|
c6740493b4 | ||
|
|
53220ae007 | ||
|
|
ccd2e7df08 | ||
|
|
ec6b3b9683 | ||
|
|
85fd377ab1 | ||
|
|
cd3c5ed9ad | ||
|
|
df3c1f6e70 | ||
|
|
561b174ddc | ||
|
|
e915e5ea19 | ||
|
|
7e09e3b5ed | ||
|
|
a63f91d25b | ||
|
|
53671a4717 | ||
|
|
5c7156a93c | ||
|
|
c72bd4fdc8 | ||
|
|
564d209402 | ||
|
|
0f2693a812 | ||
|
|
b85be2cec0 | ||
|
|
0aa01e3738 | ||
|
|
6d8bcbb27b | ||
|
|
27eb412218 | ||
|
|
038afafd33 | ||
|
|
39931b3eab | ||
|
|
151dde22f6 | ||
|
|
db57ddae1f | ||
|
|
66480706fa | ||
|
|
418c3b1331 | ||
|
|
99d281fd4f | ||
|
|
e98be80e55 | ||
|
|
f084f3a4f5 | ||
|
|
d1b7cd99e2 | ||
|
|
6b04156750 | ||
|
|
10ef69b99f | ||
|
|
ef786fd0b1 | ||
|
|
15fff9fb99 | ||
|
|
25968af711 | ||
|
|
595d0a23a9 | ||
|
|
38b4514fe1 | ||
|
|
7f2c76a7de | ||
|
|
c6c2aa767f | ||
|
|
df034046eb | ||
|
|
5dd6d79b80 | ||
|
|
5ce015afa3 | ||
|
|
259d2ee82b | ||
|
|
53c9b62390 | ||
|
|
8387fcfa9b | ||
|
|
1aa1f23ce4 | ||
|
|
1925c91a38 | ||
|
|
424c212194 | ||
|
|
29d1cdf894 | ||
|
|
7551a6b919 | ||
|
|
8bb7211ccc | ||
|
|
25512af24c | ||
|
|
3bd2f23e0d | ||
|
|
22b43adbb2 | ||
|
|
140b5cb682 | ||
|
|
291e3a0744 | ||
|
|
e830d37a9e | ||
|
|
eb9744a01d | ||
|
|
74f23ed9f1 | ||
|
|
2d0311cd15 | ||
|
|
3e49f81c76 | ||
|
|
2d2c545c10 | ||
|
|
106aa3749f | ||
|
|
89197e81e4 | ||
|
|
8ab4fba471 | ||
|
|
3ebda192e9 | ||
|
|
68964eb099 | ||
|
|
7b9fc8f280 | ||
|
|
1fe74b6d7d | ||
|
|
11f3da30c3 | ||
|
|
be401c2ebb | ||
|
|
41e6ea8280 | ||
|
|
6693c09465 | ||
|
|
51d79fbbcf | ||
|
|
9ef17eb7eb | ||
|
|
81cffd22cb | ||
|
|
c2c0201295 | ||
|
|
47cace3dd1 | ||
|
|
bfa707c11d | ||
|
|
e22774539a | ||
|
|
166ed9e825 | ||
|
|
5b55f47020 | ||
|
|
f95fe5bf6d | ||
|
|
34dda52d53 | ||
|
|
06a40240ea | ||
|
|
ad0bc14461 | ||
|
|
6d262127e7 | ||
|
|
839d7ae743 | ||
|
|
2e37c16ac7 | ||
|
|
3b99d1215e | ||
|
|
f05865972b | ||
|
|
58312b2ca2 | ||
|
|
dc206b13e2 | ||
|
|
d43bb33dcb | ||
|
|
d2ec6a0474 | ||
|
|
f69dd15474 | ||
|
|
c7935658e6 | ||
|
|
af56407296 | ||
|
|
51dd2be6ea | ||
|
|
6ebd7b94d5 | ||
|
|
22bfba739d | ||
|
|
9d27e48b2b | ||
|
|
3f26c44b5a | ||
|
|
6775659538 | ||
|
|
b80422c16e | ||
|
|
a6c1d7cf97 | ||
|
|
e6c22a04d7 | ||
|
|
56a7a7bfa8 | ||
|
|
42cd0849b4 | ||
|
|
47b622745d | ||
|
|
fbeb39ec22 | ||
|
|
8ab5b4de8d | ||
|
|
dc2f3b6802 | ||
|
|
909acf61d3 | ||
|
|
105629502a | ||
|
|
f1fff4245f | ||
|
|
d85f2df603 | ||
|
|
f6af27e960 | ||
|
|
19456800ed | ||
|
|
8dac838645 | ||
|
|
644c7eac5d | ||
|
|
90a5344f62 | ||
|
|
8751ef9b1f | ||
|
|
de0bdd8ae3 | ||
|
|
caa2fd57dd | ||
|
|
62ce3dc646 | ||
|
|
3c87721011 | ||
|
|
4c81a909a3 | ||
|
|
112db85fa1 | ||
|
|
0b08fb400e | ||
|
|
51b8a8296c | ||
|
|
be44229e5a | ||
|
|
f203bb7463 | ||
|
|
12e8baadf2 | ||
|
|
4232a5ccc9 | ||
|
|
a8700a6e8c | ||
|
|
2e31c2ff23 | ||
|
|
1323c91f1e | ||
|
|
9cff518954 | ||
|
|
308a60888b | ||
|
|
932dffce51 | ||
|
|
0a64f5cdc6 | ||
|
|
ec315e0534 | ||
|
|
b76fbefc4b | ||
|
|
7a0f5bfd18 | ||
|
|
7feaf3568f | ||
|
|
38e58702df | ||
|
|
f91695eb38 | ||
|
|
d4ba11c7a3 | ||
|
|
bd0e8f2785 | ||
|
|
fb5f3e9447 | ||
|
|
a1d3de6370 | ||
|
|
9c4853f5fb | ||
|
|
d674b2f02b | ||
|
|
1c7a7f0c82 | ||
|
|
4597384291 | ||
|
|
de3850fb1b | ||
|
|
d3809a7b5e | ||
|
|
58868d0867 | ||
|
|
286fa08b70 | ||
|
|
c8a987c43e | ||
|
|
71ad8336ce | ||
|
|
f64e6983ea | ||
|
|
52d8cdee11 | ||
|
|
60388970e0 | ||
|
|
6f2f1411a8 | ||
|
|
c5d5ff98f8 | ||
|
|
0f4435ad93 | ||
|
|
36db58307a | ||
|
|
f2e0200568 | ||
|
|
6221984a29 | ||
|
|
287bcfea8e | ||
|
|
42d94876d8 | ||
|
|
366af129bd | ||
|
|
b6d45cae4f | ||
|
|
dbc14838cd | ||
|
|
bc75505ca3 | ||
|
|
c67d663600 | ||
|
|
15b7b13ea1 | ||
|
|
338aced3a8 | ||
|
|
00b858fbbe | ||
|
|
6230c04a61 | ||
|
|
9f284a8c21 | ||
|
|
3933e380c6 | ||
|
|
6af872107b | ||
|
|
be7cd917a0 | ||
|
|
e146a79309 | ||
|
|
6e6e819150 | ||
|
|
5f8b5dbcdc | ||
|
|
586d199497 | ||
|
|
4b05e2c95f | ||
|
|
b275327469 | ||
|
|
2640d62142 | ||
|
|
0b4c2efc8f | ||
|
|
de1301e08d | ||
|
|
44485c4c72 | ||
|
|
744b6a8243 | ||
|
|
824ad97ef9 | ||
|
|
c5e161026d | ||
|
|
9092e6d18d | ||
|
|
d7d64634ee | ||
|
|
0db161efac | ||
|
|
a5f30c83b3 | ||
|
|
11755d832f | ||
|
|
27d92a238b | ||
|
|
8eb3693775 | ||
|
|
735c22cf36 | ||
|
|
62a85af9bc | ||
|
|
56295f694d | ||
|
|
4fa3a8b551 | ||
|
|
1ce83403bc |
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
|
||||
# Use bd merge for beads JSONL files
|
||||
.beads/issues.jsonl merge=beads
|
||||
@@ -0,0 +1,526 @@
|
||||
name: Update Nix Packages with nix-update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2,14 * * *" # Every 12 hours at 2 AM and 2 PM
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: "Specific package to update (optional)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: nix-update-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GIT_AUTHOR_NAME: "nix-update bot"
|
||||
GIT_AUTHOR_EMAIL: "bot@m3ta.dev"
|
||||
GIT_COMMITTER_NAME: "nix-update bot"
|
||||
GIT_COMMITTER_EMAIL: "bot@m3ta.dev"
|
||||
REPO_DIR: "/tmp/nixpkgs"
|
||||
|
||||
# Nix configuration
|
||||
NIX_PATH: "nixpkgs=channel:nixos-unstable"
|
||||
NIX_CONFIG: "experimental-features = nix-command flakes"
|
||||
|
||||
# Non-interactive mode
|
||||
DEBIAN_FRONTEND: "noninteractive"
|
||||
GIT_TERMINAL_PROMPT: "0"
|
||||
|
||||
jobs:
|
||||
nix-update:
|
||||
runs-on: nixos
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Setup Environment and Authenticate
|
||||
run: |
|
||||
if [ -d "$REPO_DIR" ]; then rm -rf "$REPO_DIR"; fi
|
||||
|
||||
git config --global credential.helper store
|
||||
echo "https://m3tam3re:${{ secrets.NIX_UPDATE_TOKEN }}@code.m3ta.dev" > ~/.git-credentials
|
||||
chmod 600 ~/.git-credentials
|
||||
|
||||
git config --global user.name "$GIT_AUTHOR_NAME"
|
||||
git config --global user.email "$GIT_AUTHOR_EMAIL"
|
||||
git config --global init.defaultBranch master
|
||||
|
||||
- name: Checkout Repository
|
||||
run: |
|
||||
git clone --no-single-branch \
|
||||
"https://m3tam3re@code.m3ta.dev/m3tam3re/nixpkgs.git" \
|
||||
"$REPO_DIR"
|
||||
|
||||
- name: Update All Flake Inputs
|
||||
id: update-flake-inputs
|
||||
run: |
|
||||
cd "$REPO_DIR"
|
||||
|
||||
echo "::group::Discovering version-pinned flake inputs"
|
||||
|
||||
# Get GitHub inputs with version refs (e.g., v1.2.9)
|
||||
VERSIONED_INPUTS=$(nix flake metadata --json | jq -r '
|
||||
.locks.nodes | to_entries[] |
|
||||
select(.value.original.type == "github") |
|
||||
select(.value.original.ref != null) |
|
||||
select(.value.original.ref | test("^v?[0-9]+\\.[0-9]+")) |
|
||||
"\(.key) \(.value.original.owner) \(.value.original.repo) \(.value.original.ref)"
|
||||
')
|
||||
|
||||
echo "Discovered version-pinned inputs:"
|
||||
echo "$VERSIONED_INPUTS"
|
||||
echo "::endgroup::"
|
||||
|
||||
UPDATED_INPUTS=""
|
||||
FAILED_INPUTS=""
|
||||
|
||||
# Update each version-pinned input
|
||||
while read -r INPUT_NAME OWNER REPO CURRENT_REF; do
|
||||
[ -z "$INPUT_NAME" ] && continue
|
||||
|
||||
echo "::group::Checking $INPUT_NAME ($OWNER/$REPO)"
|
||||
|
||||
# Get latest stable release (exclude prereleases)
|
||||
# The /releases/latest endpoint already returns the latest non-prerelease, non-draft release
|
||||
LATEST=$(curl -sf "https://api.github.com/repos/$OWNER/$REPO/releases/latest" | \
|
||||
jq -r 'if .prerelease == false then .tag_name else empty end')
|
||||
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo "⚠️ No stable release found for $INPUT_NAME (repo may only have prereleases)"
|
||||
FAILED_INPUTS="$FAILED_INPUTS $INPUT_NAME(no-stable-release)"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Current: $CURRENT_REF | Latest: $LATEST"
|
||||
|
||||
if [ "$LATEST" != "$CURRENT_REF" ]; then
|
||||
echo "Updating $INPUT_NAME from $CURRENT_REF to $LATEST"
|
||||
|
||||
# Update flake.nix
|
||||
sed -i "s|github:$OWNER/$REPO/[^\"']*|github:$OWNER/$REPO/$LATEST|g" flake.nix
|
||||
|
||||
# Update flake.lock for this input
|
||||
if nix flake update "$INPUT_NAME" 2>&1 | tee /tmp/input-update.log; then
|
||||
UPDATED_INPUTS="$UPDATED_INPUTS $INPUT_NAME($LATEST)"
|
||||
echo "✅ Updated $INPUT_NAME to $LATEST"
|
||||
else
|
||||
echo "❌ Failed to update $INPUT_NAME"
|
||||
FAILED_INPUTS="$FAILED_INPUTS $INPUT_NAME(update-failed)"
|
||||
git checkout flake.nix flake.lock 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
echo "✓ $INPUT_NAME is already up to date"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done <<< "$VERSIONED_INPUTS"
|
||||
|
||||
echo "::group::Updating non-version-pinned inputs"
|
||||
# Update all non-version-pinned inputs (branches, no-ref)
|
||||
nix flake update
|
||||
echo "::endgroup::"
|
||||
|
||||
# Check if we have any changes
|
||||
if [ -n "$(git status --porcelain flake.nix flake.lock)" ]; then
|
||||
echo "::group::Committing flake input updates"
|
||||
nix fmt flake.nix
|
||||
git add flake.nix flake.lock
|
||||
|
||||
COMMIT_MSG="chore: update flake inputs"
|
||||
[ -n "$UPDATED_INPUTS" ] && COMMIT_MSG="$COMMIT_MSG - $(echo $UPDATED_INPUTS | tr ' ' ', ')"
|
||||
|
||||
git commit -m "$COMMIT_MSG"
|
||||
echo "flake_inputs_updated=true" >> $GITHUB_OUTPUT
|
||||
echo "updated_inputs=${UPDATED_INPUTS# }" >> $GITHUB_OUTPUT
|
||||
[ -n "$FAILED_INPUTS" ] && echo "failed_inputs=${FAILED_INPUTS# }" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
else
|
||||
echo "flake_inputs_updated=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check Prerequisites
|
||||
id: check
|
||||
run: |
|
||||
cd "$REPO_DIR"
|
||||
if [ ! -d "pkgs" ]; then
|
||||
echo "❌ Error: 'pkgs' directory not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "flake.nix" ]; then
|
||||
echo "has_flake=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_flake=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Update Packages
|
||||
id: update
|
||||
run: |
|
||||
cd "$REPO_DIR"
|
||||
set -e
|
||||
|
||||
git checkout master
|
||||
|
||||
UPDATES_FOUND=false
|
||||
UPDATED_PACKAGES=""
|
||||
|
||||
check_commit() {
|
||||
[ "$1" != "$(git rev-parse HEAD)" ] && echo "true" || echo "false"
|
||||
}
|
||||
|
||||
has_update_script() {
|
||||
local pkg=$1
|
||||
# Check if package has passthru.updateScript attribute
|
||||
nix eval .#${pkg}.passthru.updateScript --json >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check if updateScript is a custom script (path-based) vs nix-update-script
|
||||
is_custom_update_script() {
|
||||
local pkg=$1
|
||||
local result
|
||||
# Custom scripts (./update.sh) become store paths ending in .sh
|
||||
# nix-update-script produces a list with nix-update binary path
|
||||
result=$(nix eval --impure --raw --expr "
|
||||
let
|
||||
flake = builtins.getFlake (toString ./.);
|
||||
pkg = flake.packages.\${builtins.currentSystem}.${pkg};
|
||||
script = pkg.passthru.updateScript or null;
|
||||
in
|
||||
if script == null then \"none\"
|
||||
else if builtins.isPath script then \"custom\"
|
||||
else if builtins.isString script then
|
||||
(if builtins.match \".*\\.sh$\" script != null then \"custom\" else \"other\")
|
||||
else if builtins.isList script then
|
||||
let first = builtins.head script;
|
||||
in if builtins.isString first && builtins.match \".*/nix-update$\" first != null
|
||||
then \"nix-update-script\"
|
||||
else \"custom\"
|
||||
else if builtins.isAttrs script && script ? command then \"custom\"
|
||||
else \"other\"
|
||||
" 2>/dev/null || echo "other")
|
||||
[[ "$result" == "custom" ]]
|
||||
}
|
||||
|
||||
# Run a custom update script directly
|
||||
# Scripts must use nix-shell shebang for their own dependencies
|
||||
run_custom_update_script() {
|
||||
local pkg=$1
|
||||
local before_hash=$(git rev-parse HEAD)
|
||||
|
||||
echo " 🔧 Detected custom update script for $pkg"
|
||||
|
||||
# Resolve the store path of the update script
|
||||
local script_path
|
||||
script_path=$(nix eval --impure --raw --expr "
|
||||
let
|
||||
flake = builtins.getFlake (toString ./.);
|
||||
pkg = flake.packages.\${builtins.currentSystem}.${pkg};
|
||||
script = pkg.passthru.updateScript;
|
||||
cmd = if builtins.isAttrs script then script.command
|
||||
else if builtins.isList script then builtins.head script
|
||||
else script;
|
||||
in toString cmd
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -z "$script_path" ]; then
|
||||
echo "❌ Could not resolve update script path for $pkg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set environment variables that nix-update would normally provide
|
||||
export UPDATE_NIX_NAME=$(nix eval --raw .#${pkg}.name 2>/dev/null || echo "$pkg")
|
||||
export UPDATE_NIX_PNAME=$(nix eval --raw .#${pkg}.pname 2>/dev/null || echo "$pkg")
|
||||
export UPDATE_NIX_OLD_VERSION=$(nix eval --raw .#${pkg}.version 2>/dev/null || echo "unknown")
|
||||
export UPDATE_NIX_ATTR_PATH="$pkg"
|
||||
|
||||
echo " Running: $script_path"
|
||||
if bash "$script_path" 2>&1 | tee /tmp/update-${pkg}.log; then
|
||||
if [ "$(check_commit "$before_hash")" = "true" ]; then
|
||||
echo "✅ Updated $pkg (via custom script)"
|
||||
return 0
|
||||
fi
|
||||
# Script succeeded but no commit — may already be up to date
|
||||
if grep -q "already at latest\|nothing to do" /tmp/update-${pkg}.log; then
|
||||
echo "✓ $pkg already up to date"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean up on failure
|
||||
git checkout -- . 2>/dev/null || true
|
||||
git clean -fd 2>/dev/null || true
|
||||
|
||||
if ! grep -q "already at latest\|nothing to do\|No new version found" /tmp/update-${pkg}.log; then
|
||||
echo "⚠️ Custom update script failed for $pkg"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
run_update() {
|
||||
local pkg=$1
|
||||
local before_hash=$(git rev-parse HEAD)
|
||||
|
||||
echo "::group::Updating $pkg"
|
||||
|
||||
# Check if this package has a custom update script
|
||||
if is_custom_update_script "$pkg"; then
|
||||
if run_custom_update_script "$pkg"; then
|
||||
echo "::endgroup::"
|
||||
return 0
|
||||
else
|
||||
echo "::endgroup::"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Standard nix-update for packages with nix-update-script
|
||||
local args=("--flake" "--commit" "--use-github-releases")
|
||||
|
||||
args+=("$pkg")
|
||||
|
||||
if nix-update "${args[@]}" 2>&1 | tee /tmp/update-${pkg}.log; then
|
||||
if [ "$(check_commit "$before_hash")" = "true" ]; then
|
||||
echo "✅ Updated $pkg"
|
||||
echo "::endgroup::"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean up any uncommitted changes from failed update
|
||||
git checkout -- . 2>/dev/null || true
|
||||
git clean -fd 2>/dev/null || true
|
||||
|
||||
echo "::endgroup::"
|
||||
|
||||
if ! grep -q "already up to date\|No new version found" /tmp/update-${pkg}.log; then
|
||||
echo "⚠️ Update failed for $pkg"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ -n "${{ inputs.package }}" ]; then
|
||||
pkg="${{ inputs.package }}"
|
||||
if [ -d "pkgs/$pkg" ]; then
|
||||
if run_update "$pkg"; then
|
||||
UPDATES_FOUND=true
|
||||
UPDATED_PACKAGES="$pkg"
|
||||
fi
|
||||
else
|
||||
echo "❌ Package 'pkgs/$pkg' not found"
|
||||
fi
|
||||
else
|
||||
# Dynamically discover packages with updateScript attribute
|
||||
echo "🔍 Discovering packages with passthru.updateScript..."
|
||||
|
||||
# Get all packages and filter those with updateScript
|
||||
ALL_PACKAGES=$(find pkgs -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort)
|
||||
UPDATABLE_PACKAGES=""
|
||||
|
||||
if [ -z "$ALL_PACKAGES" ]; then
|
||||
echo "No packages found in pkgs/"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for pkg in $ALL_PACKAGES; do
|
||||
if has_update_script "$pkg"; then
|
||||
echo " ✓ $pkg (has updateScript)"
|
||||
UPDATABLE_PACKAGES="$UPDATABLE_PACKAGES $pkg"
|
||||
else
|
||||
echo " ⊘ $pkg (no updateScript - skipping)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$UPDATABLE_PACKAGES" ]; then
|
||||
echo "ℹ️ No packages with updateScript found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Found $(echo $UPDATABLE_PACKAGES | wc -w) updatable packages"
|
||||
echo ""
|
||||
|
||||
for pkg in $UPDATABLE_PACKAGES; do
|
||||
if run_update "$pkg"; then
|
||||
UPDATES_FOUND=true
|
||||
if [ -n "$UPDATED_PACKAGES" ]; then
|
||||
UPDATED_PACKAGES="$UPDATED_PACKAGES, $pkg"
|
||||
else
|
||||
UPDATED_PACKAGES="$pkg"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
COMMIT_COUNT=$(git rev-list --count origin/master..HEAD)
|
||||
|
||||
if [ "$COMMIT_COUNT" -gt 0 ]; then
|
||||
echo "✅ $COMMIT_COUNT updates committed locally."
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "updated_packages=${UPDATED_PACKAGES}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ℹ️ No updates found."
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Verify Builds
|
||||
if: steps.update.outputs.has_updates == 'true' || steps.update-flake-inputs.outputs.flake_inputs_updated == 'true'
|
||||
run: |
|
||||
cd "$REPO_DIR"
|
||||
|
||||
echo "::group::Running flake check"
|
||||
if ! nix flake check; then
|
||||
echo "❌ Flake check failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Flake check passed"
|
||||
echo "::endgroup::"
|
||||
|
||||
IFS=', ' read -ra PKGS <<< "${{ steps.update.outputs.updated_packages }}"
|
||||
|
||||
FAILED_PACKAGES=()
|
||||
SUCCESSFUL_PACKAGES=()
|
||||
|
||||
for pkg in "${PKGS[@]}"; do
|
||||
echo "::group::Building $pkg"
|
||||
if nix build .#$pkg 2>&1 | tee /tmp/build-${pkg}.log; then
|
||||
echo "✅ Build successful for $pkg"
|
||||
SUCCESSFUL_PACKAGES+=("$pkg")
|
||||
else
|
||||
echo "❌ Build failed for $pkg"
|
||||
FAILED_PACKAGES+=("$pkg")
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
if [ ${#FAILED_PACKAGES[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "❌ Failed packages: ${FAILED_PACKAGES[*]}"
|
||||
echo "✅ Successful packages: ${SUCCESSFUL_PACKAGES[*]}"
|
||||
echo ""
|
||||
|
||||
# Upload logs as artifacts for debugging
|
||||
echo "## Build Failure Logs" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
for pkg in "${FAILED_PACKAGES[@]}"; do
|
||||
echo "### $pkg" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
||||
cat /tmp/build-${pkg}.log >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All packages built successfully: ${SUCCESSFUL_PACKAGES[*]}"
|
||||
|
||||
- name: Push Changes
|
||||
if: steps.update.outputs.has_updates == 'true' || steps.update-flake-inputs.outputs.flake_inputs_updated == 'true'
|
||||
run: |
|
||||
cd "$REPO_DIR"
|
||||
PACKAGES="${{ steps.update.outputs.updated_packages }}"
|
||||
|
||||
if [ "${{ steps.update-flake-inputs.outputs.flake_inputs_updated }}" = "true" ]; then
|
||||
UPDATED_INPUTS="${{ steps.update-flake-inputs.outputs.updated_inputs }}"
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
PACKAGES="$PACKAGES, flake inputs ($UPDATED_INPUTS)"
|
||||
else
|
||||
PACKAGES="flake inputs ($UPDATED_INPUTS)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "::group::Git Operations"
|
||||
echo "Current commit: $(git rev-parse HEAD)"
|
||||
echo "Pending commits: $(git rev-list --count origin/master..HEAD)"
|
||||
|
||||
echo ""
|
||||
echo "Pulling latest changes (rebase)..."
|
||||
if git pull --rebase origin master; then
|
||||
echo "✅ Rebase successful"
|
||||
else
|
||||
echo "⚠️ Rebase failed, resetting and retrying..."
|
||||
git rebase --abort 2>/dev/null || true
|
||||
git reset --hard origin/master
|
||||
echo "❌ Could not rebase, updates lost. Will retry next run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Pushing changes to master..."
|
||||
git push origin master
|
||||
|
||||
echo ""
|
||||
echo "✅ Successfully pushed updates for: $PACKAGES"
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
# Remove git credentials securely
|
||||
rm -f ~/.git-credentials
|
||||
git config --global --unset credential.helper 2>/dev/null || true
|
||||
|
||||
# Remove temporary directory
|
||||
rm -rf "$REPO_DIR"
|
||||
|
||||
# Remove all log files
|
||||
rm -f /tmp/update-*.log /tmp/build-*.log /tmp/opencode-build.log /tmp/update-log.txt /tmp/success-packages.txt
|
||||
|
||||
# Clear sensitive environment variables
|
||||
unset GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
HAS_UPDATES="false"
|
||||
|
||||
if [ "${{ steps.update.outputs.has_updates }}" = "true" ]; then
|
||||
HAS_UPDATES="true"
|
||||
echo "# ✅ Update Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Updated Packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`${{ steps.update.outputs.updated_packages }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ steps.update-flake-inputs.outputs.flake_inputs_updated }}" = "true" ]; then
|
||||
HAS_UPDATES="true"
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Updated Flake Inputs" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
UPDATED_INPUTS="${{ steps.update-flake-inputs.outputs.updated_inputs }}"
|
||||
if [ -n "$UPDATED_INPUTS" ]; then
|
||||
echo "$UPDATED_INPUTS" | tr ' ' '\n' | while read -r input; do
|
||||
[ -n "$input" ] && echo "- **$input**" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
fi
|
||||
FAILED_INPUTS="${{ steps.update-flake-inputs.outputs.failed_inputs }}"
|
||||
if [ -n "$FAILED_INPUTS" ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Failed Inputs" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$FAILED_INPUTS" | tr ' ' '\n' | while read -r input; do
|
||||
[ -n "$input" ] && echo "- $input" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$HAS_UPDATES" = "true" ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Status" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ All updates validated with \`nix flake check\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ All builds successful" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ Changes pushed to master" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Workflow Performance" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Started: ${{ github.event.head_commit.timestamp }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Completed: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Workflow Run: [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "# ℹ️ No Updates Required" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No updates found this run. All packages and flake inputs are up to date." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Nix build outputs
|
||||
result
|
||||
result-*
|
||||
|
||||
# Direnv
|
||||
.direnv/
|
||||
.envrc
|
||||
|
||||
# Development shells
|
||||
shell.nix
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Nix-specific
|
||||
.pre-commit-config.yaml
|
||||
.envrc.local
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
# Testing
|
||||
test-result/
|
||||
|
||||
# Local configuration (if you want to keep local overrides)
|
||||
local.nix
|
||||
flake.lock.bak
|
||||
.todos/
|
||||
|
||||
# AI agent state
|
||||
.sidecar/
|
||||
.sidecar-*
|
||||
.sisyphus/
|
||||
.sidecar-agent
|
||||
.sidecar-task
|
||||
.sidecar-pr
|
||||
.sidecar-start.sh
|
||||
.sidecar-base
|
||||
.td-root
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"success": true,
|
||||
"clones": [],
|
||||
"duplicatedLines": 0,
|
||||
"totalLines": 0,
|
||||
"percentage": 0
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"timestamp": "2026-04-15T09:30:34.459Z"
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"success": false,
|
||||
"issues": [],
|
||||
"unusedExports": [],
|
||||
"unusedFiles": [],
|
||||
"unusedDeps": [],
|
||||
"unlistedDeps": [],
|
||||
"summary": "Failed to parse output"
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"timestamp": "2026-04-15T09:30:35.667Z"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
null
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"timestamp": "2026-04-15T09:28:51.987Z"
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"items": []
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"timestamp": "2026-04-15T09:28:16.965Z"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"files": {},
|
||||
"turnCycles": 0,
|
||||
"maxCycles": 3,
|
||||
"lastUpdated": "2026-04-15T09:30:35.668Z"
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
# m3ta-nixpkgs Knowledge Base
|
||||
|
||||
**Generated:** 2026-02-14
|
||||
**Commit:** dc2f3b6
|
||||
**Branch:** master
|
||||
|
||||
## OVERVIEW
|
||||
|
||||
Personal Nix flake: custom packages, overlays, NixOS/Home Manager modules, dev shells. Flakes-only (no channels).
|
||||
|
||||
## STRUCTURE
|
||||
|
||||
```
|
||||
.
|
||||
├── flake.nix # Entry: packages, overlays, modules, shells, lib
|
||||
├── pkgs/ # Custom packages (one dir each, callPackage registry)
|
||||
├── modules/
|
||||
│ ├── nixos/ # System modules (ports.nix)
|
||||
│ └── home-manager/ # User modules by category (cli/, coding/, ports.nix)
|
||||
├── lib/ # Shared utilities (ports.nix)
|
||||
├── shells/ # Dev environments (default, python, devops)
|
||||
├── overlays/mods/ # Package modifications (n8n version bump)
|
||||
├── templates/ # Boilerplate for new packages/modules
|
||||
├── examples/ # Usage examples
|
||||
└── .gitea/workflows/ # CI/CD workflows (nix-update automation)
|
||||
```
|
||||
|
||||
## WHERE TO LOOK
|
||||
|
||||
| Task | Location | Notes |
|
||||
| -------------------- | ---------------------------------- | ------------------------------------- |
|
||||
| Add package | `pkgs/<name>/default.nix` | Register in `pkgs/default.nix` |
|
||||
| Add NixOS module | `modules/nixos/<name>.nix` | Import in `modules/nixos/default.nix` |
|
||||
| Add HM module | `modules/home-manager/<category>/` | Category: cli, coding, or root |
|
||||
| Override nixpkgs pkg | `overlays/mods/<name>.nix` | Import in `overlays/mods/default.nix` |
|
||||
| Add dev shell | `shells/<name>.nix` | Register in `shells/default.nix` |
|
||||
| Use port management | `config.m3ta.ports.get "service"` | Host-specific via `hostOverrides` |
|
||||
| CI/CD workflows | `.gitea/workflows/<name>.yml` | Automated package updates (nix-update) |
|
||||
|
||||
## CONVENTIONS
|
||||
|
||||
**Formatter**: `nix fmt` before commit (alejandra)
|
||||
|
||||
**Naming**:
|
||||
|
||||
- Packages: `lowercase-hyphen` (e.g., `hyprpaper-random`)
|
||||
- Variables: `camelCase` (e.g., `portHelpers`)
|
||||
- Module options: `m3ta.*` namespace
|
||||
|
||||
**Imports**: Multi-line, trailing commas:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
```
|
||||
|
||||
**Modules**: Standard pattern:
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "description";
|
||||
};
|
||||
config = mkIf cfg.enable { ... };
|
||||
}
|
||||
```
|
||||
|
||||
**Meta**: Always include all fields:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
description = "...";
|
||||
homepage = "...";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "...";
|
||||
};
|
||||
```
|
||||
|
||||
## PACKAGE PATTERNS
|
||||
|
||||
**Rust**: `rustPlatform.buildRustPackage rec { cargoLock.lockFile = src + "/Cargo.lock"; }`
|
||||
|
||||
**Shell**: `writeShellScriptBin "name" ''script''` or `mkDerivation` with custom `installPhase`
|
||||
|
||||
**AppImage**: `appimageTools.wrapType2 { ... }`
|
||||
|
||||
**Custom fetcher**: `fetchFromGitea { domain = "code.m3ta.dev"; owner = "m3tam3re"; ... }`
|
||||
|
||||
## MODULE PATTERNS
|
||||
|
||||
**Simple**: `options.cli.name = { enable = mkEnableOption "..."; }; config = mkIf cfg.enable { ... };`
|
||||
|
||||
**Multiple**: `config = mkMerge [ (mkIf cfg.x.enable { ... }) (mkIf cfg.y.enable { ... }) ];`
|
||||
|
||||
**Shared lib**: `portsLib = import ../../lib/ports.nix { inherit lib; }; portHelpers = portsLib.mkPortHelpers { ... };`
|
||||
|
||||
## LIBRARY FUNCTIONS
|
||||
|
||||
### `lib.ports`
|
||||
|
||||
Port management utilities. See [Port Management](#port-management).
|
||||
|
||||
### `lib.agents`
|
||||
|
||||
Harness-agnostic agent management. Reads canonical `agent.toml` +
|
||||
`system-prompt.md` from the AGENTS flake input and renders tool-specific configs.
|
||||
|
||||
**Functions:**
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|--------|
|
||||
| `loadCanonical { agentsInput }` | Load canonical agents from AGENTS flake |
|
||||
| `renderForOpencode { pkgs, canonical, modelOverrides }` | Render to OpenCode file-based agents |
|
||||
| `renderForClaudeCode { pkgs, canonical, modelOverrides }` | Render to Claude Code agents + settings.json |
|
||||
| `renderForPi { pkgs, canonical, modelOverrides, primaryAgent }` | Render to Pi AGENTS.md + SYSTEM.md + agents/ |
|
||||
| `renderForTool { pkgs, agentsInput, tool, modelOverrides }` | Dispatch to correct renderer by tool name |
|
||||
| `shellHookForTool { pkgs, agentsInput, tool, modelOverrides }` | Generate devShell shellHook (symlinks rendered files) |
|
||||
|
||||
### `lib.coding-rules`
|
||||
|
||||
Coding rules injection. Generates `coding-rules.json` + symlinks rules from
|
||||
the AGENTS repository. The old `lib.opencode-rules` name still works.
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|--------|
|
||||
| `mkCodingRules { agents, languages, concerns, frameworks, rulesDir }` | Generate rules config + shellHook. `rulesDir` defaults to `.opencode-rules` |
|
||||
| `mkOpencodeRules` | Backward-compat alias for `mkCodingRules` |
|
||||
|
||||
## PORT MANAGEMENT
|
||||
|
||||
Central port management: `config.m3ta.ports.get "service"` with host-specific via `hostOverrides`
|
||||
|
||||
Generated: `/etc/m3ta/ports.json` (NixOS), `~/.config/m3ta/ports.json` (HM)
|
||||
|
||||
## COMMANDS
|
||||
|
||||
```bash
|
||||
nix flake check # Validate flake
|
||||
nix fmt # Format (alejandra)
|
||||
nix build .#<pkg> # Build package
|
||||
nix flake show # List outputs
|
||||
nix develop # Enter dev shell
|
||||
nix develop .#python # Python shell
|
||||
nix develop .#devops # DevOps shell
|
||||
|
||||
# In dev shell only:
|
||||
statix check . # Lint
|
||||
deadnix . # Find dead code
|
||||
```
|
||||
|
||||
## ANTI-PATTERNS
|
||||
|
||||
| Don't | Do Instead |
|
||||
| ------------------------- | ------------------------------------------------------------------- |
|
||||
| `lib.fakeHash` in commits | Get real hash: `nix build`, copy from error |
|
||||
| Flat module files | Organize by category (`cli/`, `coding/`) |
|
||||
| Hardcode ports | Use `m3ta.ports` module |
|
||||
| Skip meta fields | Include all: description, homepage, license, platforms, mainProgram |
|
||||
| `with pkgs;` in modules | Explicit `pkgs.package` or `with pkgs; [ ... ]` in lists only |
|
||||
|
||||
## COMMIT FORMAT
|
||||
|
||||
```
|
||||
type: brief description
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `chore`
|
||||
|
||||
## NOTES
|
||||
|
||||
- **Hash fetching**: Use `lib.fakeHash` initially, build to get real hash
|
||||
- **HM modules**: Category subdirs (`cli/`, `coding/`) have own `default.nix` aggregators
|
||||
- **Ports module**: Different for NixOS vs HM (HM adds `generateEnvVars` option)
|
||||
- **Overlays**: `modifications` overlay uses `{prev}:` pattern, not `{final, prev}:`
|
||||
- **Dev shell tools**: `statix`, `deadnix` only available inside `nix develop`
|
||||
- **Automated package updates**: Packages are automatically updated weekly via Gitea Actions using `nix-update`. Review PRs from the automation before merging. For urgent updates, manually run the workflow or update manually.
|
||||
|
||||
## Task Management
|
||||
|
||||
**td** is an optional task-tracking package. See `docs/packages/td.md` for details.
|
||||
|
||||
## Agent System Architecture
|
||||
|
||||
The agent system uses harness-agnostic canonical definitions stored as
|
||||
`agent.toml` + `system-prompt.md` in the AGENTS repository. Renderers in
|
||||
`lib/agents.nix` transform these into tool-specific configs at build time.
|
||||
|
||||
### How it works
|
||||
|
||||
1. **Canonical definitions** live in the AGENTS repo as `agent.toml` files
|
||||
(one per agent) with shared fields: name, description, mode, systemPrompt,
|
||||
permissions, skills.
|
||||
2. **`loadCanonical`** reads all agent definitions from the AGENTS flake input.
|
||||
3. **Renderers** produce tool-specific output:
|
||||
- `renderForOpencode` → `*.md` files with YAML frontmatter for `.opencode/agents/`
|
||||
- `renderForClaudeCode` → `.claude/agents/*.md` + `.claude/settings.json` with permission rules
|
||||
- `renderForPi` → `AGENTS.md`, `SYSTEM.md`, `agents/*.md` for Pi's subagent format
|
||||
4. **`renderForTool`** dispatches to the correct renderer by tool name
|
||||
(`"opencode"`, `"claude-code"`, or `"pi"`).
|
||||
5. **`shellHookForTool`** generates a devShell shellHook that symlinks rendered
|
||||
files into the project directory.
|
||||
6. **HM modules** in `modules/home-manager/coding/agents/` handle per-tool
|
||||
Home Manager integration.
|
||||
|
||||
### Key files in this repo
|
||||
|
||||
- `lib/agents.nix` — renderers, dispatcher, shellHook generator
|
||||
- `lib/coding-rules.nix` — coding rules injection (`mkCodingRules`)
|
||||
- `modules/home-manager/coding/agents/` — per-tool HM sub-modules (opencode, claude-code, pi)
|
||||
- `modules/home-manager/coding/opencode.nix` — OpenCode HM module (slimmed, agents handled separately)
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Remove duplicate opencode-rules.nix (backward-compat alias preserved)
|
||||
- Tool-agnostic naming in coding-rules lib internals
|
||||
- Remove redundant overlay entries for non-existent flake inputs
|
||||
- Remove redundant 'additions' overlay (identical to 'default')
|
||||
|
||||
### Removed
|
||||
- Dead overlay entries for non-existent flake inputs
|
||||
|
||||
## [0.4.0] - 2026-04-15
|
||||
|
||||
### Added
|
||||
- Pi-agent wrapper with systemd sandbox and per-host-user policy
|
||||
- Containerized Pi agent
|
||||
- `lib.agents.nix` with loadCanonical, renderers (OpenCode, Claude Code, Pi), and shellHook
|
||||
- `lib.coding-rules` helper for per-project rule injection (renamed from opencode-rules)
|
||||
- Home Manager modules for coding agents: `claude-code`, `opencode`, `pi`
|
||||
- Agents rework with canonical TOML format and harness-agnostic renderers
|
||||
- `vibetyper` and `eigent` packages
|
||||
- `openspec` package
|
||||
- `basecamp-cli` package
|
||||
- `openshell` package (0.0.14 through 0.0.23)
|
||||
- `openwork` package
|
||||
- Opencode config moved into m3ta-nixpkgs
|
||||
- Opencode dev shell with mkCodingRules demo
|
||||
|
||||
### Changed
|
||||
- OpenCode flake input updated through v1.1.65 to v1.3.6
|
||||
- Switched from local opencode package to upstream flake input
|
||||
- Removed opencode-desktop (awaiting upstream fix), later re-enabled
|
||||
- Nix eval warnings resolved
|
||||
- Flake inputs updated throughout
|
||||
|
||||
### Fixed
|
||||
- Pi settings sync
|
||||
- Remove openwork sidecars in preFixup to prevent .opencode-wrapped conflict
|
||||
- Remove sidecar binaries from openwork $out/bin to fix buildEnv conflict
|
||||
- Vibetyper .desktop entry
|
||||
- Opencode module formatting
|
||||
- Formatting opencode module
|
||||
|
||||
## [0.3.0] - 2026-02-20
|
||||
|
||||
### Added
|
||||
- `notesmd-cli` package with flake checks
|
||||
- `sidecar` and `td` packages
|
||||
- `opencode-desktop` package with Wayland support
|
||||
- `mem0` package (1.0.2 through 1.0.9)
|
||||
- `kestracli` / `kestractl` package (1.0.0 to 1.2.2)
|
||||
|
||||
### Changed
|
||||
- Nix-update CI workflow optimized with caching and parallel processing
|
||||
- Restructured n8n version handling for nix-update compatibility
|
||||
- Switched formatter from nixpkgs-fmt to alejandra
|
||||
- Replace local opencode with upstream flake input v1.1.27
|
||||
|
||||
### Fixed
|
||||
- n8n build error
|
||||
- n8n pnpm hash
|
||||
- n8n update script
|
||||
- Gitea runner opencode.url flake input
|
||||
- nix-update workflow: YAML syntax, jobs indentation, PR body formatting
|
||||
- Arithmetic increment failing with set -e in nix-update workflow
|
||||
- Removed magic-nix-cache-action causing platform mapping error
|
||||
- Opencode bun version requirement patched to match upstream lockfile
|
||||
- Deprecated opencode update logic removed
|
||||
- nix fmt without arg in workflow
|
||||
- Extra Lua config renamed initLua
|
||||
- Stt-ptt use pkill for better process management
|
||||
|
||||
## [0.2.0] - 2026-01-13
|
||||
|
||||
### Added
|
||||
- Gitea Actions workflow for automated package updates with nix-update
|
||||
- `n8n`, `beads`, and `opencode` packages
|
||||
- `stt-ptt` package with auto-language detection
|
||||
- `rofi-project-opener` for rofi-based project launching
|
||||
- Hierarchical AGENTS.md knowledge base
|
||||
- Dev shell structure with python and devops shells
|
||||
- Port management modules (NixOS + Home Manager)
|
||||
- Port helper library (`lib/ports.nix`)
|
||||
|
||||
### Changed
|
||||
- Beads updated through v0.49.1
|
||||
- N8n updated through v2.8.1
|
||||
- Opencode updated through v1.1.18
|
||||
- Documentation expanded with comprehensive patterns and HM module docs
|
||||
|
||||
### Fixed
|
||||
- Python env version fix for marimo
|
||||
|
||||
## [0.1.0] - 2025-10-04
|
||||
|
||||
### Added
|
||||
- Initial flake setup with packages, overlays, modules, and shells
|
||||
- NixOS and Home Manager module infrastructure
|
||||
- `lib/` shared utilities
|
||||
- `overlays/mods/` for package modifications
|
||||
- `templates/` for new packages/modules
|
||||
- `examples/` for usage documentation
|
||||
@@ -1,7 +0,0 @@
|
||||
This repository is being used as a Dolt remote.
|
||||
|
||||
ref=refs/dolt/data
|
||||
|
||||
head=43d141bca1faebdf2ed20deb3c147ca1b2946eea
|
||||
|
||||
timestamp=2026-07-04T07:30:30Z
|
||||
@@ -0,0 +1,90 @@
|
||||
# m3ta-nixpkgs
|
||||
|
||||
Personal Nix flake repository: custom packages, overlays, NixOS modules, and Home Manager modules.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎁 **Custom Packages**: Collection of personal Nix packages
|
||||
- 🔄 **Overlays**: Package modifications and enhancements
|
||||
- 🐚 **Development Shells**: Pre-configured environments (Python, DevOps)
|
||||
- ⚙️ **NixOS Modules**: System-level configuration modules
|
||||
- 🏠 **Home Manager Modules**: User-level configuration modules
|
||||
- 📚 **Library Functions**: Helper utilities for configuration management
|
||||
- ❄️ **Flakes Only**: Modern Nix flakes support (no channels)
|
||||
|
||||
## Quick Links
|
||||
|
||||
- 📖 [Full Documentation](./docs)
|
||||
- 🚀 [Quick Start Guide](./docs/QUICKSTART.md)
|
||||
- 📚 [Architecture](./docs/ARCHITECTURE.md)
|
||||
- 🤝 [Contributing](./docs/CONTRIBUTING.md)
|
||||
- 📦 [Packages](./docs/packages/)
|
||||
- ⚙️ [Modules](./docs/modules/)
|
||||
- 📖 [Guides](./docs/guides/)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Add to your flake
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
m3ta-nixpkgs.url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
|
||||
# Build a package
|
||||
nix build git+https://code.m3ta.dev/m3tam3re/nixpkgs#code2prompt
|
||||
|
||||
# Run a package
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#zellij-ps
|
||||
```
|
||||
|
||||
## Available Packages
|
||||
|
||||
| Package | Description |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| `code2prompt` | Convert code to prompts |
|
||||
| `hyprpaper-random` | Random wallpaper setter for Hyprpaper |
|
||||
| `kestractl` | CLI for the Kestra workflow orchestration platform |
|
||||
| `launch-webapp` | Launch web applications |
|
||||
| `mem0` | AI memory assistant with vector storage |
|
||||
| `msty-studio` | Msty Studio application |
|
||||
| `n8n` | Free and source-available fair-code licensed workflow automation tool |
|
||||
| `notesmd-cli` | Obsidian CLI (Community) - Interact with Obsidian in the terminal |
|
||||
| `opencode-desktop` | OpenCode Desktop App with Wayland support (includes workaround for upstream issue #11755) |
|
||||
| `pomodoro-timer` | Pomodoro timer utility |
|
||||
| `rofi-project-opener` | Rofi-based project launcher |
|
||||
| `sidecar` | Companion tool for CLI agents with diffs, file trees, and task management |
|
||||
| `stt-ptt` | Push to Talk Speech to Text |
|
||||
| `td` | Minimalist CLI for tracking tasks across AI coding sessions |
|
||||
| `tuxedo-backlight` | Backlight control for Tuxedo laptops |
|
||||
| `zellij-ps` | Project switcher for Zellij |
|
||||
|
||||
## Automated Package Updates
|
||||
|
||||
This repository uses Gitea Actions to automatically update packages using `nix-update`:
|
||||
|
||||
**Workflow**: [`.gitea/workflows/nix-update.yml`](./.gitea/workflows/nix-update.yml)
|
||||
|
||||
**Schedule**: Runs weekly on Sundays, and can be triggered manually.
|
||||
|
||||
**What it does**:
|
||||
- Checks all packages in `pkgs/` for updates
|
||||
- Updates versions and hashes using `nix-update --flake --commit`
|
||||
- Creates a new branch with updates
|
||||
- Opens a pull request automatically
|
||||
|
||||
**Manual Trigger**:
|
||||
Go to **Actions → Update Nix Packages with nix-update → Run workflow** in Gitea UI, then optionally specify a specific package to update.
|
||||
|
||||
**Setup Required**:
|
||||
1. Create a Personal Access Token in Gitea (Settings → Applications → Generate Token)
|
||||
2. Token scopes needed: `user`, `repo`, `write:issue`
|
||||
3. Add token as secret: Settings → Secrets → New → `NIX_UPDATE_TOKEN`
|
||||
|
||||
For detailed usage, module documentation, package references, and contribution guidelines, see the [full documentation](./docs).
|
||||
|
||||
## License
|
||||
|
||||
Individual packages may have their own licenses. Check each package's `meta.license` attribute.
|
||||
|
||||
## Maintainer
|
||||
|
||||
[@m3tam3re](https://m3ta.dev)
|
||||
@@ -0,0 +1,473 @@
|
||||
# Architecture
|
||||
|
||||
Understanding the design and structure of m3ta-nixpkgs.
|
||||
|
||||
## Overview
|
||||
|
||||
m3ta-nixpkgs is organized as a modern Nix flake with a focus on reusability, consistency, and maintainability. The repository follows clear conventions and patterns to make it easy to understand, extend, and contribute to.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
m3ta-nixpkgs/
|
||||
├── flake.nix # Main entry point, defines all outputs
|
||||
├── pkgs/ # Custom packages (callPackage registry)
|
||||
│ ├── default.nix # Package registry (entry point)
|
||||
│ ├── code2prompt/ # Individual packages
|
||||
│ ├── hyprpaper-random/
|
||||
│ ├── mem0/
|
||||
│ └── ...
|
||||
├── modules/
|
||||
│ ├── nixos/ # NixOS modules
|
||||
│ │ ├── default.nix # Module aggregator
|
||||
│ │ ├── mem0.nix
|
||||
│ │ └── ports.nix
|
||||
│ └── home-manager/ # Home Manager modules
|
||||
│ ├── default.nix # Module aggregator
|
||||
│ ├── ports.nix
|
||||
│ ├── cli/ # Categorized modules
|
||||
│ │ ├── default.nix
|
||||
│ │ └── zellij-ps.nix
|
||||
│ └── coding/
|
||||
│ ├── default.nix
|
||||
│ └── editors.nix
|
||||
├── lib/ # Shared utilities
|
||||
│ ├── default.nix # Library aggregator
|
||||
│ └── ports.nix # Port management functions
|
||||
├── shells/ # Development environments
|
||||
│ ├── default.nix # Shell registry
|
||||
│ ├── python.nix
|
||||
│ └── devops.nix
|
||||
├── overlays/ # Package modifications
|
||||
│ ├── default.nix # Overlay aggregator
|
||||
│ └── mods/
|
||||
│ └── default.nix # Individual overlays
|
||||
├── templates/ # Boilerplate for new items
|
||||
│ ├── package/
|
||||
│ ├── nixos-module/
|
||||
│ └── home-manager-module/
|
||||
├── examples/ # Usage examples
|
||||
│ ├── nixos-configuration.nix
|
||||
│ └── home-manager-standalone.nix
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Flake Outputs
|
||||
|
||||
`flake.nix` is the entry point that defines all outputs:
|
||||
|
||||
### Packages
|
||||
|
||||
```nix
|
||||
packages = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./pkgs {inherit pkgs;});
|
||||
```
|
||||
|
||||
- Built for all supported systems
|
||||
- Uses `callPackage` pattern for lazy evaluation
|
||||
- Available via `nix build .#<package-name>`
|
||||
|
||||
### Overlays
|
||||
|
||||
```nix
|
||||
overlays = {
|
||||
# Default overlay: adds all custom packages
|
||||
default = final: prev: import ./pkgs {pkgs = final;};
|
||||
|
||||
# Additions overlay: same as default
|
||||
additions = final: prev: import ./pkgs {pkgs = final;};
|
||||
|
||||
# Modifications overlay: modifies existing nixpkgs packages
|
||||
modifications = final: prev: import ./overlays/mods {inherit prev;};
|
||||
};
|
||||
```
|
||||
|
||||
- `default`: Adds all custom packages to nixpkgs
|
||||
- `additions`: Individual package additions
|
||||
- `modifications`: Overrides existing packages
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
```nix
|
||||
nixosModules = {
|
||||
default = ./modules/nixos; # Import all modules
|
||||
ports = ./modules/nixos/ports.nix; # Specific module
|
||||
mem0 = ./modules/nixos/mem0.nix;
|
||||
};
|
||||
```
|
||||
|
||||
- System-level configuration modules
|
||||
- Use `m3ta.*` namespace
|
||||
- Can import all modules or individual ones
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
```nix
|
||||
homeManagerModules = {
|
||||
default = import ./modules/home-manager;
|
||||
ports = import ./modules/home-manager/ports.nix;
|
||||
zellij-ps = import ./modules/home-manager/zellij-ps.nix;
|
||||
};
|
||||
```
|
||||
|
||||
- User-level configuration modules
|
||||
- Categorized by function (cli, coding)
|
||||
- Use `m3ta.*` namespace
|
||||
|
||||
### Library Functions
|
||||
|
||||
```nix
|
||||
lib = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./lib {lib = pkgs.lib;});
|
||||
```
|
||||
|
||||
- Helper functions for configuration
|
||||
- Port management utilities
|
||||
- Can be used in your configurations
|
||||
|
||||
### Development Shells
|
||||
|
||||
```nix
|
||||
devShells = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./shells {inherit pkgs;});
|
||||
```
|
||||
|
||||
- Pre-configured development environments
|
||||
- Available: `default`, `python`, `devops`
|
||||
- Usage: `nix develop .#<shell-name>`
|
||||
|
||||
### Templates
|
||||
|
||||
```nix
|
||||
templates = {
|
||||
package = {
|
||||
path = ./templates/package;
|
||||
description = "Template for a new package";
|
||||
};
|
||||
nixos-module = {
|
||||
path = ./templates/nixos-module;
|
||||
description = "Template for a new NixOS module";
|
||||
};
|
||||
home-manager-module = {
|
||||
path = ./templates/home-manager-module;
|
||||
description = "Template for a new Home Manager module";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
- Boilerplate for quick start
|
||||
- Usage: `nix flake init -t .#template-name`
|
||||
|
||||
## Package Organization
|
||||
|
||||
### Registry Pattern
|
||||
|
||||
`pkgs/default.nix` acts as a central registry:
|
||||
|
||||
```nix
|
||||
{
|
||||
inherit (pkgs) callPackage;
|
||||
} rec {
|
||||
code2prompt = callPackage ./code2prompt {};
|
||||
hyprpaper-random = callPackage ./hyprpaper-random {};
|
||||
mem0 = callPackage ./mem0 {};
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Lazy evaluation: only builds requested packages
|
||||
- Consistent interface: all packages use `callPackage`
|
||||
- Easy discovery: one file lists all packages
|
||||
|
||||
### Package Structure
|
||||
|
||||
Each package lives in its own directory:
|
||||
|
||||
```
|
||||
pkgs/your-package/
|
||||
├── default.nix # Package definition
|
||||
├── source.py # Optional: source files
|
||||
└── README.md # Optional: package documentation
|
||||
```
|
||||
|
||||
**Conventions**:
|
||||
- Directory name matches registry attribute
|
||||
- Use `callPackage` for dependencies
|
||||
- Always include `meta` with all fields
|
||||
|
||||
### Common Package Patterns
|
||||
|
||||
#### Rust Packages
|
||||
|
||||
```nix
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "myapp";
|
||||
version = "1.0.0";
|
||||
src = fetchFromGitHub { ... };
|
||||
cargoLock.lockFile = src + "/Cargo.lock";
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Python Packages
|
||||
|
||||
```nix
|
||||
python3.pkgs.buildPythonPackage rec {
|
||||
pname = "mypythonapp";
|
||||
version = "1.0.0";
|
||||
src = fetchFromGitHub { ... };
|
||||
dependencies = with python3.pkgs; [requests click];
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Shell Scripts
|
||||
|
||||
```nix
|
||||
writeShellScriptBin "myscript" ''
|
||||
#!/usr/bin/env bash
|
||||
echo "Hello World"
|
||||
''
|
||||
```
|
||||
|
||||
#### AppImage
|
||||
|
||||
```nix
|
||||
appimageTools.wrapType2 rec {
|
||||
name = "myapp";
|
||||
src = fetchurl { ... };
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
## Module Organization
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
Located in `modules/nixos/`:
|
||||
|
||||
```
|
||||
modules/nixos/
|
||||
├── default.nix # Imports all modules
|
||||
├── ports.nix # Port management
|
||||
├── mem0.nx # Individual module
|
||||
```
|
||||
|
||||
**Pattern**:
|
||||
|
||||
```nix
|
||||
{config, lib, pkgs, ...}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "description";
|
||||
# ... options
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# ... configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
Located in `modules/home-manager/` with categories:
|
||||
|
||||
```
|
||||
modules/home-manager/
|
||||
├── default.nix # Imports all modules
|
||||
├── ports.nix # Port management
|
||||
├── cli/
|
||||
│ ├── default.nix # Aggregates CLI modules
|
||||
│ └── zellij-ps.nix
|
||||
└── coding/
|
||||
├── default.nix # Aggregates coding modules
|
||||
└── editors.nix
|
||||
```
|
||||
|
||||
**Categories**:
|
||||
- `cli/`: Command-line tools and utilities
|
||||
- `coding/`: Development tools and editors
|
||||
|
||||
**Pattern** (same as NixOS):
|
||||
|
||||
```nix
|
||||
{config, lib, pkgs, ...}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.coding.editors;
|
||||
in {
|
||||
options.m3ta.coding.editors = {
|
||||
enable = mkEnableOption "editor configuration";
|
||||
# ... options
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# ... configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Library Functions
|
||||
|
||||
Located in `lib/`:
|
||||
|
||||
```nix
|
||||
{lib}: {
|
||||
# Port management utilities
|
||||
ports = import ./ports.nix {inherit lib;};
|
||||
}
|
||||
```
|
||||
|
||||
### Port Management
|
||||
|
||||
Centralized port management across hosts:
|
||||
|
||||
```nix
|
||||
# Usage in configuration
|
||||
portHelpers = inputs.m3ta-nixpkgs.lib.${system}.ports.mkPortHelpers myPorts;
|
||||
|
||||
# Get port with host override
|
||||
services.nginx.port = portHelpers.getPort "nginx" "laptop";
|
||||
|
||||
# Get all ports for host
|
||||
allLaptopPorts = portHelpers.getHostPorts "laptop";
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Single source of truth for ports
|
||||
- Host-specific overrides
|
||||
- Avoid port conflicts
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Context | Convention | Example |
|
||||
|---------|------------|---------|
|
||||
| Packages | `lowercase-hyphen` | `hyprpaper-random` |
|
||||
| Variables | `camelCase` | `portHelpers` |
|
||||
| Module options | `m3ta.*` | `m3ta.ports.enable` |
|
||||
| Files | `lowercase-hyphen` | `my-module.nix` |
|
||||
| Directories | `lowercase-hyphen` | `cli/`, `coding/` |
|
||||
|
||||
## Code Patterns
|
||||
|
||||
### Module Options
|
||||
|
||||
Always use `mkEnableOption` for enable flags:
|
||||
|
||||
```nix
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "description";
|
||||
};
|
||||
```
|
||||
|
||||
### Conditional Configuration
|
||||
|
||||
Use `mkIf` for conditional config:
|
||||
|
||||
```nix
|
||||
config = mkIf cfg.enable {
|
||||
# Only applied when cfg.enable is true
|
||||
};
|
||||
```
|
||||
|
||||
### Multiple Conditions
|
||||
|
||||
Use `mkMerge` for multiple conditions:
|
||||
|
||||
```nix
|
||||
config = mkMerge [
|
||||
(mkIf cfg.feature1.enable { ... })
|
||||
(mkIf cfg.feature2.enable { ... })
|
||||
];
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
||||
Multi-line, trailing commas:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
```
|
||||
|
||||
### Meta Fields
|
||||
|
||||
Always include all fields:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
description = "...";
|
||||
homepage = "...";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "program-name";
|
||||
};
|
||||
```
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Flakes-Only
|
||||
|
||||
**Decision**: Use flakes exclusively, no channels.
|
||||
|
||||
**Rationale**:
|
||||
- Reproducible builds
|
||||
- Explicit dependencies
|
||||
- Better integration with modern Nix tooling
|
||||
|
||||
### CallPackage Pattern
|
||||
|
||||
**Decision**: Use `callPackage` for all packages.
|
||||
|
||||
**Rationale**:
|
||||
- Lazy evaluation
|
||||
- Clear dependency graph
|
||||
- Consistent interface
|
||||
|
||||
### Module Categorization
|
||||
|
||||
**Decision**: Categorize Home Manager modules by function.
|
||||
|
||||
**Rationale**:
|
||||
- Easier to find related modules
|
||||
- Logical organization
|
||||
- Follows user mental model
|
||||
|
||||
### Port Management
|
||||
|
||||
**Decision**: Centralized port management with host overrides.
|
||||
|
||||
**Rationale**:
|
||||
- Avoid port conflicts
|
||||
- Easy to manage multiple hosts
|
||||
- Single source of truth
|
||||
|
||||
### Namespace Convention
|
||||
|
||||
**Decision**: Use `m3ta.*` namespace for all modules.
|
||||
|
||||
**Rationale**:
|
||||
- Avoid conflicts
|
||||
- Clear attribution
|
||||
- Easy to discover
|
||||
|
||||
## Supported Systems
|
||||
|
||||
- `x86_64-linux` - Primary development target
|
||||
- `aarch64-linux` - ARM Linux
|
||||
- `x86_64-darwin` - macOS Intel
|
||||
- `aarch64-darwin` - macOS Apple Silicon
|
||||
|
||||
**Note**: Some packages may be Linux-only (check `meta.platforms`).
|
||||
@@ -0,0 +1,373 @@
|
||||
# Contributing
|
||||
|
||||
Contributing to m3ta-nixpkgs.
|
||||
|
||||
## Setting Up Development Environment
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://code.m3ta.dev/m3tam3re/nixpkgs.git
|
||||
cd nixpkgs
|
||||
|
||||
# Enter development shell (includes linting tools)
|
||||
nix develop
|
||||
|
||||
# Or use a specific shell
|
||||
nix develop .#python
|
||||
nix develop .#devops
|
||||
```
|
||||
|
||||
## Code Style and Formatting
|
||||
|
||||
### Formatting
|
||||
|
||||
Use alejandra to format Nix files:
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
nix fmt
|
||||
|
||||
# Format specific file
|
||||
alejandra path/to/file.nix
|
||||
```
|
||||
|
||||
**Always run `nix fmt` before committing.**
|
||||
|
||||
### Linting
|
||||
|
||||
Linting tools are only available inside the dev shell:
|
||||
|
||||
```bash
|
||||
# Enter dev shell first
|
||||
nix develop
|
||||
|
||||
# Run statix (linter)
|
||||
statix check .
|
||||
|
||||
# Run deadnix (find dead code)
|
||||
deadnix .
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
### Naming
|
||||
|
||||
- **Packages**: `lowercase-hyphen` (e.g., `hyprpaper-random`)
|
||||
- **Variables**: `camelCase` (e.g., `portHelpers`)
|
||||
- **Module options**: `m3ta.*` namespace
|
||||
- **Files**: `lowercase-hyphen` (e.g., `my-module.nix`)
|
||||
|
||||
### Imports
|
||||
|
||||
Multi-line, trailing commas:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
```
|
||||
|
||||
### Meta Fields
|
||||
|
||||
Always include all fields in package definitions:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
description = "Short description";
|
||||
homepage = "https://github.com/author/repo";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "program-name";
|
||||
};
|
||||
```
|
||||
|
||||
### Module Pattern
|
||||
|
||||
Standard module pattern:
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "description";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Adding a Package
|
||||
|
||||
1. Create package directory in `pkgs/your-package/`
|
||||
2. Write `default.nix` with package definition
|
||||
3. Register in `pkgs/default.nix`
|
||||
|
||||
See [Adding Packages Guide](./guides/adding-packages.md) for detailed instructions.
|
||||
|
||||
**Note**: Package versions are automatically updated weekly via Gitea Actions using `nix-update`. You don't need to worry about keeping versions current - the automation will create PRs for updates. Just focus on ensuring the package builds and works correctly.
|
||||
|
||||
### Package Testing
|
||||
|
||||
```bash
|
||||
# Build the package
|
||||
nix build .#your-package
|
||||
|
||||
# Test if package runs
|
||||
nix run .#your-package -- --help
|
||||
|
||||
# Check with linter
|
||||
nix develop
|
||||
statix check pkgs/your-package/
|
||||
```
|
||||
|
||||
## Adding a NixOS Module
|
||||
|
||||
1. Create module file in `modules/nixos/your-module.nix`
|
||||
2. Import in `modules/nixos/default.nix` (or use directly)
|
||||
|
||||
```nix
|
||||
# modules/nixos/default.nix
|
||||
{
|
||||
imports = [
|
||||
./your-module.nix
|
||||
./other-module.nix
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Adding a Home Manager Module
|
||||
|
||||
1. Choose appropriate category: `cli/`, `coding/`, or root
|
||||
2. Create module file
|
||||
3. Import in category's `default.nix` or root `default.nix`
|
||||
|
||||
```nix
|
||||
# modules/home-manager/cli/default.nix
|
||||
{
|
||||
imports = [
|
||||
./your-tool.nix
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Making Changes
|
||||
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/your-change
|
||||
|
||||
# Make changes
|
||||
|
||||
# Format code
|
||||
nix fmt
|
||||
|
||||
# Test builds
|
||||
nix build .#your-package
|
||||
nix flake check
|
||||
|
||||
# Lint
|
||||
nix develop
|
||||
statix check .
|
||||
deadnix .
|
||||
```
|
||||
|
||||
### Commit Format
|
||||
|
||||
Use conventional commits:
|
||||
|
||||
```
|
||||
type: brief description
|
||||
|
||||
Types:
|
||||
- feat: New feature
|
||||
- fix: Bug fix
|
||||
- docs: Documentation changes
|
||||
- style: Code style changes (formatting)
|
||||
- refactor: Code refactoring
|
||||
- chore: Maintenance tasks
|
||||
- test: Adding or updating tests
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
feat: add new package for myapp
|
||||
fix: resolve port conflict in mem0 module
|
||||
docs: update installation instructions
|
||||
style: format nix files
|
||||
refactor: simplify port management
|
||||
chore: update dependencies
|
||||
```
|
||||
|
||||
### Before Committing
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
nix fmt
|
||||
|
||||
# Validate flake
|
||||
nix flake check
|
||||
|
||||
# Run linters
|
||||
nix develop
|
||||
statix check .
|
||||
deadnix .
|
||||
|
||||
# Add files
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "type: description"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Package Testing
|
||||
|
||||
```bash
|
||||
# Build for specific system
|
||||
nix build .#your-package --system x86_64-linux
|
||||
|
||||
# Test on different systems
|
||||
nix build .#your-package --system aarch64-linux
|
||||
```
|
||||
|
||||
### Module Testing
|
||||
|
||||
```bash
|
||||
# Test NixOS configuration
|
||||
sudo nixos-rebuild test --flake .#hostname
|
||||
|
||||
# Test Home Manager configuration
|
||||
home-manager switch --flake .#username@hostname
|
||||
```
|
||||
|
||||
### Flake Validation
|
||||
|
||||
```bash
|
||||
# Validate all outputs
|
||||
nix flake check
|
||||
|
||||
# Show all outputs
|
||||
nix flake show
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
|
||||
### Before Submitting
|
||||
|
||||
1. [ ] Code formatted with `nix fmt`
|
||||
2. [ ] Passes `statix check .`
|
||||
3. [ ] Passes `deadnix .`
|
||||
4. [ ] Passes `nix flake check`
|
||||
5. [ ] New packages include `meta` fields
|
||||
6. [ ] Documentation updated if needed
|
||||
7. [ ] Commit messages follow convention
|
||||
|
||||
### Handling Automated Update PRs
|
||||
|
||||
The repository has automated package updates via Gitea Actions (see main README for details). When reviewing automated update PRs:
|
||||
|
||||
1. **Build and test**: Verify the updated package builds successfully
|
||||
2. **Check changelinks**: Review upstream release notes for breaking changes
|
||||
3. **Test functionality**: Ensure the package still works as expected
|
||||
4. **Review package definition**: Check if any manual adjustments are needed
|
||||
|
||||
For urgent updates, you can manually trigger the workflow from the Gitea UI or update the package manually.
|
||||
|
||||
### PR Description
|
||||
|
||||
Include:
|
||||
- What changed and why
|
||||
- How to test
|
||||
- Any breaking changes
|
||||
- Related issues
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hash Errors
|
||||
|
||||
When building packages, you may encounter hash errors:
|
||||
|
||||
```
|
||||
got: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
expected: sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=
|
||||
```
|
||||
|
||||
**Solution**: Copy the `got` hash and update the package:
|
||||
|
||||
```nix
|
||||
src = fetchFromGitHub {
|
||||
# ...
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Use actual hash
|
||||
};
|
||||
```
|
||||
|
||||
### Dependency Not Found
|
||||
|
||||
If a package isn't found, check:
|
||||
1. Package registered in `pkgs/default.nix`
|
||||
2. Overlay applied in your configuration
|
||||
3. System matches supported platform
|
||||
|
||||
### Linting Errors
|
||||
|
||||
```bash
|
||||
# Fix statix issues manually or use auto-fix where available
|
||||
statix fix .
|
||||
|
||||
# Review deadnix suggestions
|
||||
deadnix -e .
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check existing packages and modules for patterns
|
||||
- Read [Architecture](./ARCHITECTURE.md) for design decisions
|
||||
- Review [Code Patterns](./reference/patterns.md) for conventions
|
||||
- Open an issue for questions
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Don't | Do Instead |
|
||||
|-------|------------|
|
||||
| `lib.fakeHash` in commits | Get real hash: `nix build`, copy from error |
|
||||
| Flat module files | Organize by category (`cli/`, `coding/`) |
|
||||
| Hardcode ports | Use `m3ta.ports` module |
|
||||
| Skip meta fields | Include all: description, homepage, license, platforms, mainProgram |
|
||||
| `with pkgs;` in modules | Explicit `pkgs.package` or `with pkgs; [ ... ]` in lists only |
|
||||
| Suppress type errors | Fix underlying type issues |
|
||||
| Delete tests to "pass" | Fix failing tests |
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [ ] Follows naming conventions
|
||||
- [ ] Properly formatted (`nix fmt`)
|
||||
- [ ] Passes linting (`statix`, `deadnix`)
|
||||
- [ ] Has complete `meta` fields (packages)
|
||||
- [ ] Documentation updated if needed
|
||||
- [ ] No dead code
|
||||
- [ ] No obvious bugs or issues
|
||||
- [ ] Appropriate for scope
|
||||
|
||||
## Release Process
|
||||
|
||||
This is a personal repository, but semantic versioning is followed for tags:
|
||||
|
||||
1. Update versions as needed
|
||||
2. Update changelog
|
||||
3. Tag release
|
||||
4. Push tags
|
||||
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
```
|
||||
@@ -0,0 +1,316 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get started with m3ta-nixpkgs in 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Nix with flakes enabled (Nix 2.4+)
|
||||
- Basic familiarity with NixOS and/or Home Manager
|
||||
|
||||
Enable flakes in `/etc/nixos/configuration.nix`:
|
||||
|
||||
```nix
|
||||
nix.settings.experimental-features = ["nix-command" "flakes"];
|
||||
```
|
||||
|
||||
## Adding to Your Flake
|
||||
|
||||
### NixOS Configuration
|
||||
|
||||
```nix
|
||||
{
|
||||
description = "My NixOS configuration";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
m3ta-nixpkgs,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations = {
|
||||
hostname = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
./hardware-configuration.nix
|
||||
|
||||
# Import m3ta's NixOS modules
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
|
||||
# Apply overlay to make packages available
|
||||
({pkgs, ...}: {
|
||||
nixpkgs.overlays = [m3ta-nixpkgs.overlays.default];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
# Regular nixpkgs packages
|
||||
vim
|
||||
];
|
||||
})
|
||||
|
||||
home-manager.nixosModules.home-manager
|
||||
{
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
home-manager.users.yourusername = {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Home Manager
|
||||
|
||||
```nix
|
||||
{
|
||||
description = "My Home Manager configuration";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
m3ta-nixpkgs,
|
||||
} @ inputs: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [m3ta-nixpkgs.overlays.default];
|
||||
};
|
||||
in {
|
||||
homeConfigurations.yourusername = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
extraSpecialArgs = {inherit inputs;};
|
||||
modules = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
{
|
||||
home.username = "yourusername";
|
||||
home.homeDirectory = "/home/yourusername";
|
||||
home.packages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Using Packages Without Configuration
|
||||
|
||||
You can build and run packages directly without adding to your configuration:
|
||||
|
||||
```bash
|
||||
# Build a package
|
||||
nix build git+https://code.m3ta.dev/m3tam3re/nixpkgs#code2prompt
|
||||
|
||||
# Run a package
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#zellij-ps
|
||||
|
||||
# List all available packages
|
||||
nix flake show git+https://code.m3ta.dev/m3tam3re/nixpkgs
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Use a Package System-Wide
|
||||
|
||||
```nix
|
||||
# In configuration.nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt # From m3ta-nixpkgs
|
||||
git # From nixpkgs
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Use a Package for Your User Only
|
||||
|
||||
```nix
|
||||
# In home.nix
|
||||
{pkgs, ...}: {
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
zellij-ps
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Use a NixOS Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
# Or import the default which includes all modules
|
||||
];
|
||||
|
||||
# Enable mem0 service
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Use Port Management
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080; # Override on laptop
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Available Packages
|
||||
|
||||
| Package | Description |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| `code2prompt` | Convert code to prompts |
|
||||
| `hyprpaper-random` | Random wallpaper setter for Hyprpaper |
|
||||
| `launch-webapp` | Launch web applications |
|
||||
| `mem0` | AI memory assistant with vector storage |
|
||||
| `msty-studio` | Msty Studio application |
|
||||
| `pomodoro-timer` | Pomodoro timer utility |
|
||||
| `tuxedo-backlight` | Backlight control for Tuxedo laptops |
|
||||
| `zellij-ps` | Project switcher for Zellij |
|
||||
|
||||
## Essential Commands
|
||||
|
||||
```bash
|
||||
# Validate your configuration
|
||||
nix flake check
|
||||
|
||||
# Format Nix files
|
||||
nix fmt
|
||||
|
||||
# Apply NixOS configuration
|
||||
sudo nixos-rebuild switch
|
||||
|
||||
# Apply Home Manager configuration
|
||||
home-manager switch
|
||||
|
||||
# Enter development shell
|
||||
nix develop .#python # Python shell
|
||||
nix develop .#devops # DevOps shell
|
||||
|
||||
# List all outputs
|
||||
nix flake show
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://code.m3ta.dev/m3tam3re/nixpkgs.git
|
||||
cd nixpkgs
|
||||
|
||||
# Create a new package
|
||||
nix flake init -t .#package
|
||||
|
||||
# Test package build
|
||||
nix build .#your-package
|
||||
|
||||
# Run linting (in dev shell)
|
||||
nix develop
|
||||
statix check .
|
||||
deadnix .
|
||||
|
||||
# Format before commit
|
||||
nix fmt
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Package Not Found
|
||||
|
||||
Make sure you applied the overlay:
|
||||
|
||||
```nix
|
||||
nixpkgs.overlays = [m3ta-nixpkgs.overlays.default];
|
||||
```
|
||||
|
||||
Or reference directly:
|
||||
|
||||
```nix
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.package-name
|
||||
```
|
||||
|
||||
### Module Not Found
|
||||
|
||||
Make sure you imported the module:
|
||||
|
||||
```nix
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
# or specific module:
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
```
|
||||
|
||||
### Hash Mismatch
|
||||
|
||||
If you're developing and get a hash error, rebuild to get the real hash:
|
||||
|
||||
```bash
|
||||
nix build .#your-package
|
||||
# Copy hash from error and update package
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Architecture](./ARCHITECTURE.md) - Understanding the repository structure
|
||||
- [Adding Packages](./guides/adding-packages.md) - How to add new packages
|
||||
- [Using Modules](./guides/using-modules.md) - Deep dive into modules
|
||||
- [Port Management](./guides/port-management.md) - Managing service ports
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
# m3ta-nixpkgs Documentation
|
||||
|
||||
Complete documentation for m3ta's personal Nix flake repository.
|
||||
|
||||
## Overview
|
||||
|
||||
m3ta-nixpkgs is a collection of custom packages, overlays, NixOS modules, and Home Manager modules organized as a modern Nix flake. This repository follows a flakes-only approach (no channels) and provides reusable components for personal infrastructure.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- **[Quick Start Guide](./QUICKSTART.md)** - Get up and running in 5 minutes
|
||||
- **[Architecture](./ARCHITECTURE.md)** - Understanding the repository structure and design
|
||||
- **[Contributing](./CONTRIBUTING.md)** - How to contribute to this repository
|
||||
|
||||
## Documentation Sections
|
||||
|
||||
### 📚 Guides
|
||||
|
||||
Step-by-step guides for common tasks:
|
||||
|
||||
- [Getting Started](./guides/getting-started.md) - Initial setup and basic usage
|
||||
- [Adding Packages](./guides/adding-packages.md) - How to add new packages
|
||||
- [Port Management](./guides/port-management.md) - Managing service ports across hosts
|
||||
- [Using Modules](./guides/using-modules.md) - Using NixOS and Home Manager modules
|
||||
- [Development Workflow](./guides/development-workflow.md) - Development and testing workflow
|
||||
|
||||
### 📦 Packages
|
||||
|
||||
Documentation for all custom packages:
|
||||
|
||||
- [code2prompt](./packages/code2prompt.md) - Convert code to prompts
|
||||
- [hyprpaper-random](./packages/hyprpaper-random.md) - Random wallpaper setter for Hyprpaper
|
||||
- [kestractl](./packages/kestractl.md) - CLI for the Kestra workflow orchestration platform
|
||||
- [launch-webapp](./packages/launch-webapp.md) - Launch web applications
|
||||
- [mem0](./packages/mem0.md) - AI memory assistant with vector storage
|
||||
- [msty-studio](./packages/msty-studio.md) - Msty Studio application
|
||||
- [n8n](./packages/n8n.md) - Free and source-available fair-code licensed workflow automation tool
|
||||
- [notesmd-cli](./packages/notesmd-cli.md) - Obsidian CLI (Community) - Interact with Obsidian in the terminal
|
||||
- [pomodoro-timer](./packages/pomodoro-timer.md) - Pomodoro timer utility
|
||||
- [rofi-project-opener](./packages/rofi-project-opener.md) - Rofi-based project launcher with custom args
|
||||
- [sidecar](./packages/sidecar.md) - Companion tool for CLI agents with diffs, file trees, and task management
|
||||
- [stt-ptt](./packages/stt-ptt.md) - Push to Talk Speech to Text using Whisper
|
||||
- [td](./packages/td.md) - Minimalist CLI for tracking tasks across AI coding sessions
|
||||
- [tuxedo-backlight](./packages/tuxedo-backlight.md) - Backlight control for Tuxedo laptops
|
||||
- [zellij-ps](./packages/zellij-ps.md) - Project switcher for Zellij
|
||||
|
||||
### ⚙️ Modules
|
||||
|
||||
Configuration modules for NixOS and Home Manager:
|
||||
|
||||
#### NixOS Modules
|
||||
- [Overview](./modules/nixos/overview.md) - NixOS modules overview
|
||||
- [mem0](./modules/nixos/mem0.md) - Mem0 REST API server module
|
||||
- [ports](./modules/nixos/ports.md) - Port management module
|
||||
|
||||
#### Home Manager Modules
|
||||
- [Overview](./modules/home-manager/overview.md) - Home Manager modules overview
|
||||
- [CLI Tools](./modules/home-manager/cli/) - CLI-related modules
|
||||
- [rofi-project-opener](./modules/home-manager/cli/rofi-project-opener.md) - Rofi-based project launcher
|
||||
- [stt-ptt](./modules/home-manager/cli/stt-ptt.md) - Push to Talk Speech to Text
|
||||
- [zellij-ps](./modules/home-manager/cli/zellij-ps.md) - Zellij project switcher
|
||||
- [Coding](./modules/home-manager/coding/) - Development-related modules
|
||||
- [editors](./modules/home-manager/coding/editors.md) - Editor configurations
|
||||
|
||||
### 📖 Reference
|
||||
|
||||
Technical references and APIs:
|
||||
|
||||
- [Functions](./reference/functions.md) - Library functions documentation
|
||||
- [Patterns](./reference/patterns.md) - Code patterns and anti-patterns
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
m3ta-nixpkgs/
|
||||
├── docs/ # This directory
|
||||
│ ├── README.md
|
||||
│ ├── QUICKSTART.md
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ ├── CONTRIBUTING.md
|
||||
│ ├── guides/
|
||||
│ ├── packages/
|
||||
│ ├── modules/
|
||||
│ └── reference/
|
||||
├── pkgs/ # Custom packages
|
||||
├── modules/
|
||||
│ ├── nixos/ # NixOS modules
|
||||
│ └── home-manager/ # Home Manager modules
|
||||
├── lib/ # Library functions
|
||||
├── shells/ # Development shells
|
||||
├── overlays/ # Package overlays
|
||||
├── templates/ # Templates
|
||||
└── examples/ # Usage examples
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Flakes-Only Approach
|
||||
|
||||
This repository uses modern Nix flakes exclusively. No channels or `nix-channel` commands are needed. All dependencies are declaratively specified in `flake.nix`.
|
||||
|
||||
### Namespace Convention
|
||||
|
||||
All modules use the `m3ta.*` namespace:
|
||||
|
||||
- `m3ta.ports.*` - Port management
|
||||
- `m3ta.mem0.*` - Mem0 service configuration
|
||||
- `m3ta.*.enable` - Enable/disable modules
|
||||
|
||||
### Port Management
|
||||
|
||||
Centralized port management across hosts using the `m3ta.ports` module:
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = { mem0 = 8000; };
|
||||
hostOverrides.laptop = { mem0 = 8080; };
|
||||
currentHost = "laptop";
|
||||
};
|
||||
```
|
||||
|
||||
## Supported Systems
|
||||
|
||||
- `x86_64-linux` - Primary
|
||||
- `aarch64-linux` - ARM Linux
|
||||
- `x86_64-darwin` - macOS (Intel)
|
||||
- `aarch64-darwin` - macOS (Apple Silicon)
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Validate flake
|
||||
nix flake check
|
||||
|
||||
# Format code
|
||||
nix fmt
|
||||
|
||||
# Build package
|
||||
nix build .#<package-name>
|
||||
|
||||
# List outputs
|
||||
nix flake show
|
||||
|
||||
# Enter dev shell
|
||||
nix develop
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Individual packages may have their own licenses. Check each package's `meta.license` attribute.
|
||||
|
||||
## Maintainer
|
||||
|
||||
[@m3tam3re](https://m3ta.dev)
|
||||
@@ -0,0 +1,504 @@
|
||||
# Adding Packages Guide
|
||||
|
||||
How to add new packages to m3ta-nixpkgs.
|
||||
|
||||
## Overview
|
||||
|
||||
Packages in m3ta-nixpkgs are organized using a `callPackage` registry pattern. Each package lives in its own directory and is registered centrally.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using Templates
|
||||
|
||||
Use the package template for quick setup:
|
||||
|
||||
```bash
|
||||
nix flake init -t .#package my-new-package
|
||||
```
|
||||
|
||||
This creates a template structure in `templates/package/` that you can copy.
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
**Important**: This repository uses automated package updates via Gitea Actions and `nix-update`. When adding a new package:
|
||||
|
||||
- Use any stable, working version for the initial package
|
||||
- You don't need to use the absolute latest version
|
||||
- The automation will keep the package updated automatically on a weekly basis
|
||||
- Review and merge automated update PRs as they come in
|
||||
|
||||
See the main README.md for more details on the automated update workflow.
|
||||
|
||||
### Manual Setup
|
||||
|
||||
1. Create directory: `pkgs/your-package/`
|
||||
2. Write `default.nix` with package definition
|
||||
3. Register in `pkgs/default.nix`
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
pkgs/your-package/
|
||||
├── default.nix # Package definition (required)
|
||||
├── source.py # Optional: additional source files
|
||||
├── wrapper.sh # Optional: wrapper scripts
|
||||
└── README.md # Optional: package documentation
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Rust Package
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "my-rust-app";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "author";
|
||||
repo = "my-rust-app";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
cargoLock.lockFile = src + "/Cargo.lock";
|
||||
|
||||
buildInputs = [openssl];
|
||||
|
||||
nativeBuildInputs = [pkg-config];
|
||||
|
||||
meta = with lib; {
|
||||
description = "My Rust application";
|
||||
homepage = "https://github.com/author/my-rust-app";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "my-rust-app";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Python Package
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
python3,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
python3.pkgs.buildPythonPackage rec {
|
||||
pname = "my-python-app";
|
||||
version = "1.0.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "author";
|
||||
repo = "my-python-app";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
build-system = with python3.pkgs; [setuptools];
|
||||
|
||||
dependencies = with python3.pkgs; [
|
||||
requests
|
||||
click
|
||||
];
|
||||
|
||||
optional-dependencies = with python3.pkgs; {
|
||||
extras = [pyyaml];
|
||||
};
|
||||
|
||||
doCheck = true;
|
||||
|
||||
pythonImportsCheck = ["myapp"];
|
||||
|
||||
meta = with lib; {
|
||||
description = "My Python application";
|
||||
homepage = "https://github.com/author/my-python-app";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "my-app";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Shell Script Package
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
writeShellScriptBin,
|
||||
}:
|
||||
writeShellScriptBin "my-script" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "Hello from my script!"
|
||||
|
||||
# Your script logic here
|
||||
''
|
||||
|
||||
# If you need to add dependencies
|
||||
{
|
||||
lib,
|
||||
writeShellApplication,
|
||||
bash,
|
||||
curl,
|
||||
}:
|
||||
writeShellApplication {
|
||||
name = "my-script";
|
||||
runtimeInputs = [bash curl];
|
||||
text = ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
curl -s https://example.com
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
### AppImage Package
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
appimageTools,
|
||||
fetchurl,
|
||||
}:
|
||||
appimageTools.wrapType2 rec {
|
||||
name = "my-app";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/author/my-app/releases/download/v${version}/My-App-${version}.AppImage";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
description = "My AppImage application";
|
||||
homepage = "https://github.com/author/my-app";
|
||||
license = licenses.unfree;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = name;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Source with Patch
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
fetchpatch,
|
||||
buildGoModule,
|
||||
}:
|
||||
buildGoModule rec {
|
||||
pname = "my-go-app";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "author";
|
||||
repo = "my-go-app";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# Add local patch
|
||||
./fix-build.patch
|
||||
|
||||
# Add patch from URL
|
||||
(fetchpatch {
|
||||
url = "https://github.com/author/my-app/pull/123.patch";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
})
|
||||
];
|
||||
|
||||
vendorHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "My Go application";
|
||||
homepage = "https://github.com/author/my-go-app";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "my-app";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Package with Custom Installation
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
makeWrapper,
|
||||
}:
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "my-app";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "author";
|
||||
repo = "my-app";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [makeWrapper];
|
||||
|
||||
buildPhase = ''
|
||||
make build
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
install -Dm755 my-app $out/bin/my-app
|
||||
|
||||
# Wrap with runtime dependencies
|
||||
wrapProgram $out/bin/my-app \
|
||||
--prefix PATH : ${lib.makeBinPath [some-dep]}
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "My custom application";
|
||||
homepage = "https://github.com/author/my-app";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "my-app";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Registration
|
||||
|
||||
### Register in `pkgs/default.nix`
|
||||
|
||||
Add your package to the registry:
|
||||
|
||||
```nix
|
||||
{
|
||||
inherit (pkgs) callPackage;
|
||||
} rec {
|
||||
# Existing packages
|
||||
code2prompt = callPackage ./code2prompt {};
|
||||
zellij-ps = callPackage ./zellij-ps {};
|
||||
|
||||
# Your new package
|
||||
my-new-package = callPackage ./my-new-package {};
|
||||
}
|
||||
```
|
||||
|
||||
### With Custom Arguments
|
||||
|
||||
If your package needs custom arguments:
|
||||
|
||||
```nix
|
||||
# pkgs/default.nix
|
||||
{
|
||||
inherit (pkgs) callPackage;
|
||||
} rec {
|
||||
my-new-package = callPackage ./my-new-package {
|
||||
customArg = "value";
|
||||
};
|
||||
}
|
||||
|
||||
# pkgs/my-new-package/default.nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchurl,
|
||||
customArg, # This will be passed from the registry
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Hashes
|
||||
|
||||
### Using `lib.fakeHash`
|
||||
|
||||
During development, use `lib.fakeHash` to get the real hash:
|
||||
|
||||
```nix
|
||||
src = fetchFromGitHub {
|
||||
owner = "author";
|
||||
repo = "my-app";
|
||||
rev = "v${version}";
|
||||
hash = lib.fakeHash; # Temporary placeholder
|
||||
};
|
||||
```
|
||||
|
||||
Build the package:
|
||||
|
||||
```bash
|
||||
nix build .#my-new-package
|
||||
```
|
||||
|
||||
Copy the actual hash from the error message and update the package:
|
||||
|
||||
```nix
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Real hash
|
||||
```
|
||||
|
||||
**Important**: Never commit `lib.fakeHash` to the repository.
|
||||
|
||||
## Testing
|
||||
|
||||
### Build Package
|
||||
|
||||
```bash
|
||||
nix build .#my-new-package
|
||||
```
|
||||
|
||||
### Test Execution
|
||||
|
||||
```bash
|
||||
nix run .#my-new-package -- --help
|
||||
```
|
||||
|
||||
### Run in Shell
|
||||
|
||||
```bash
|
||||
nix shell .#my-new-package
|
||||
my-new-app --version
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
statix check pkgs/my-new-package/
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Meta Fields
|
||||
|
||||
Always include complete `meta` information:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
description = "Short one-line description";
|
||||
longDescription = ''
|
||||
Longer description explaining what the package does,
|
||||
its features, and use cases.
|
||||
'';
|
||||
homepage = "https://github.com/author/repo";
|
||||
changelog = "https://github.com/author/repo/releases/tag/v${version}";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "program-name";
|
||||
};
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Explicitly declare all dependencies:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
# Runtime dependencies
|
||||
openssl,
|
||||
curl,
|
||||
# Native build dependencies
|
||||
pkg-config,
|
||||
cmake,
|
||||
}:
|
||||
```
|
||||
|
||||
### Versioning
|
||||
|
||||
Use `rec` to reference `version` in multiple places:
|
||||
|
||||
```nix
|
||||
rec {
|
||||
pname = "my-app";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
rev = "v${version}";
|
||||
# ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Platform Restrictions
|
||||
|
||||
If package is platform-specific:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
# Linux only
|
||||
platforms = platforms.linux;
|
||||
|
||||
# Or specific platforms
|
||||
platforms = ["x86_64-linux" "aarch64-linux"];
|
||||
|
||||
# Or exclude platforms
|
||||
broken = stdenv.isDarwin;
|
||||
};
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hash Mismatch
|
||||
|
||||
Error: `got: sha256-AAAAAAAA... expected: sha256-BBBBBB...`
|
||||
|
||||
Solution: Copy `got` hash and update package definition.
|
||||
|
||||
### Dependency Not Found
|
||||
|
||||
Error: `error: undefined variable 'somedep'`
|
||||
|
||||
Solution: Add dependency to function arguments and build inputs:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
somedep, # Add here
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
buildInputs = [somedep]; # Add here
|
||||
}
|
||||
```
|
||||
|
||||
### Import Check Failure
|
||||
|
||||
Error: `error: Python module 'mymodule' not found`
|
||||
|
||||
Solution: Disable or fix imports check:
|
||||
|
||||
```nix
|
||||
pythonImportsCheck = ["mymodule"]; # Check this is correct
|
||||
# Or if importing creates side effects:
|
||||
pythonImportsCheck = [];
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See existing packages in the repository:
|
||||
|
||||
- `pkgs/code2prompt/` - Rust package
|
||||
- `pkgs/mem0/` - Python package
|
||||
- `pkgs/hyprpaper-random/` - Shell script
|
||||
- `pkgs/msty-studio/` - AppImage
|
||||
- `pkgs/zellij-ps/` - Fetch from Gitea
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding package organization
|
||||
- [Using Modules](./using-modules.md) - If you need to create modules
|
||||
- [Contributing](../CONTRIBUTING.md) - Code style and guidelines
|
||||
@@ -0,0 +1,527 @@
|
||||
# Development Workflow Guide
|
||||
|
||||
Development, testing, and contribution workflow for m3ta-nixpkgs.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### Clone Repository
|
||||
|
||||
```bash
|
||||
git clone https://code.m3ta.dev/m3tam3re/nixpkgs.git
|
||||
cd nixpkgs
|
||||
```
|
||||
|
||||
### Enter Development Shell
|
||||
|
||||
```bash
|
||||
# Default shell (includes linting tools)
|
||||
nix develop
|
||||
|
||||
# Python shell
|
||||
nix develop .#python
|
||||
|
||||
# DevOps shell
|
||||
nix develop .#devops
|
||||
```
|
||||
|
||||
### Development Shell Tools
|
||||
|
||||
The default dev shell includes:
|
||||
|
||||
- `statix` - Nix linter
|
||||
- `deadnix` - Find dead code
|
||||
- `alejandra` - Code formatter
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Create Feature Branch
|
||||
|
||||
```bash
|
||||
# Checkout main and pull latest
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/my-new-package
|
||||
```
|
||||
|
||||
### 2. Make Changes
|
||||
|
||||
```bash
|
||||
# Create new package
|
||||
mkdir -p pkgs/my-package
|
||||
vim pkgs/my-package/default.nix
|
||||
|
||||
# Or modify existing package
|
||||
vim pkgs/existing-package/default.nix
|
||||
|
||||
# Or add module
|
||||
vim modules/nixos/my-module.nix
|
||||
```
|
||||
|
||||
### 3. Format Code
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
nix fmt
|
||||
|
||||
# Format specific file
|
||||
alejandra path/to/file.nix
|
||||
```
|
||||
|
||||
**Always format before committing.**
|
||||
|
||||
### 4. Test Changes
|
||||
|
||||
#### Build Package
|
||||
|
||||
```bash
|
||||
# Build specific package
|
||||
nix build .#my-package
|
||||
|
||||
# Build all packages
|
||||
nix build .#packages.x86_64-linux
|
||||
```
|
||||
|
||||
#### Run Package
|
||||
|
||||
```bash
|
||||
# Test if package runs
|
||||
nix run .#my-package -- --help
|
||||
|
||||
# Or enter shell with package
|
||||
nix shell .#my-package
|
||||
my-package --version
|
||||
```
|
||||
|
||||
#### Validate Flake
|
||||
|
||||
```bash
|
||||
# Validate all outputs
|
||||
nix flake check
|
||||
|
||||
# Show all outputs
|
||||
nix flake show
|
||||
```
|
||||
|
||||
#### Test Module
|
||||
|
||||
```bash
|
||||
# Test NixOS configuration
|
||||
sudo nixos-rebuild test --flake .#hostname
|
||||
|
||||
# Test Home Manager configuration
|
||||
home-manager switch --flake .#username@hostname
|
||||
```
|
||||
|
||||
### 5. Lint Code
|
||||
|
||||
```bash
|
||||
# Enter dev shell for linting tools
|
||||
nix develop
|
||||
|
||||
# Run statix
|
||||
statix check .
|
||||
|
||||
# Run deadnix
|
||||
deadnix .
|
||||
|
||||
# Fix auto-fixable issues
|
||||
statix fix .
|
||||
```
|
||||
|
||||
### 6. Commit Changes
|
||||
|
||||
```bash
|
||||
# Stage changes
|
||||
git add .
|
||||
|
||||
# Commit with conventional format
|
||||
git commit -m "feat: add my-package for doing X"
|
||||
|
||||
# Commit types: feat, fix, docs, style, refactor, chore, test
|
||||
```
|
||||
|
||||
### 7. Push and Create PR
|
||||
|
||||
```bash
|
||||
# Push branch
|
||||
git push origin feature/my-new-package
|
||||
|
||||
# Create PR via web interface or CLI
|
||||
gh pr create --title "feat: add my-package" --body "Description of changes"
|
||||
```
|
||||
|
||||
## Testing Strategies
|
||||
|
||||
### Local Testing
|
||||
|
||||
#### Test Package Build
|
||||
|
||||
```bash
|
||||
# Build for current system
|
||||
nix build .#my-package
|
||||
|
||||
# Build for specific system
|
||||
nix build .#my-package --system aarch64-linux
|
||||
|
||||
# Build for macOS
|
||||
nix build .#my-package --system x86_64-darwin
|
||||
```
|
||||
|
||||
#### Test Package Functionality
|
||||
|
||||
```bash
|
||||
# Enter shell with package
|
||||
nix shell .#my-package
|
||||
|
||||
# Run the program
|
||||
my-package --help
|
||||
my-package --version
|
||||
|
||||
# Test with sample data
|
||||
echo "test" | my-package
|
||||
```
|
||||
|
||||
#### Test Configuration
|
||||
|
||||
```bash
|
||||
# Test NixOS configuration
|
||||
sudo nixos-rebuild test --flake .#hostname
|
||||
|
||||
# Test Home Manager configuration
|
||||
home-manager switch --flake .#username@hostname
|
||||
|
||||
# Check configuration syntax
|
||||
nix eval .#nixosConfigurations.hostname.config --apply builtins.attrNames
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
#### Test with Real Services
|
||||
|
||||
```bash
|
||||
# If package is a service
|
||||
# 1. Add to configuration
|
||||
# 2. Apply configuration
|
||||
sudo nixos-rebuild switch
|
||||
|
||||
# 3. Test service
|
||||
systemctl status my-service
|
||||
journalctl -u my-service -f
|
||||
|
||||
# 4. Test functionality
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
#### Test with Dependencies
|
||||
|
||||
```bash
|
||||
# Build dependency chain
|
||||
nix build .#my-package \
|
||||
--rebuild \
|
||||
--keep-going
|
||||
|
||||
# Check if dependencies are satisfied
|
||||
nix path-info .#my-package --references
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### Pre-Commit Hook
|
||||
|
||||
Create `.git/hooks/pre-commit`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Format code
|
||||
nix fmt
|
||||
|
||||
# Lint
|
||||
statix check .
|
||||
deadnix .
|
||||
|
||||
# Validate
|
||||
nix flake check
|
||||
|
||||
# Test build
|
||||
nix build .#your-package
|
||||
```
|
||||
|
||||
Make executable:
|
||||
|
||||
```bash
|
||||
chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
### Pre-Push Hook
|
||||
|
||||
Create `.git/hooks/pre-push`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Validate flake
|
||||
nix flake check
|
||||
|
||||
# Run tests if they exist
|
||||
# make test
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Build Failures
|
||||
|
||||
#### Hash Mismatch
|
||||
|
||||
Error: `got: sha256-AAAAAAAA... expected: sha256-BBBBBB...`
|
||||
|
||||
Solution: Copy the `got` hash and update package:
|
||||
|
||||
```nix
|
||||
src = fetchFromGitHub {
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Use actual hash
|
||||
};
|
||||
```
|
||||
|
||||
#### Dependency Not Found
|
||||
|
||||
Error: `error: undefined variable 'somelib'`
|
||||
|
||||
Solution: Check function arguments:
|
||||
|
||||
```nix
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
somelib, # Add this if missing
|
||||
}:
|
||||
```
|
||||
|
||||
#### Build Failed
|
||||
|
||||
Error: `builder for '/nix/store/...' failed`
|
||||
|
||||
Solution:
|
||||
|
||||
```bash
|
||||
# Build with verbose output
|
||||
nix build .#my-package -v --show-trace
|
||||
|
||||
# Check build logs
|
||||
nix log .#my-package
|
||||
|
||||
# Enter build environment for debugging
|
||||
nix shell -f .#my-package .bashInteractive
|
||||
```
|
||||
|
||||
### Runtime Failures
|
||||
|
||||
#### Package Not Executable
|
||||
|
||||
Error: `error: operation not permitted`
|
||||
|
||||
Solution: Check `mainProgram` in meta:
|
||||
|
||||
```nix
|
||||
meta = with lib; {
|
||||
mainProgram = "my-app"; # Must match executable name
|
||||
};
|
||||
```
|
||||
|
||||
#### Library Not Found
|
||||
|
||||
Error: `error while loading shared libraries: libfoo.so`
|
||||
|
||||
Solution: Add to build inputs:
|
||||
|
||||
```nix
|
||||
buildInputs = [someLib];
|
||||
```
|
||||
|
||||
### Configuration Failures
|
||||
|
||||
#### Option Not Found
|
||||
|
||||
Error: `error: The option 'm3ta.mymodule' does not exist`
|
||||
|
||||
Solution: Import the module:
|
||||
|
||||
```nix
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Update Package Version
|
||||
|
||||
```bash
|
||||
# 1. Update version in package definition
|
||||
vim pkgs/my-package/default.nix
|
||||
|
||||
# 2. Build to get new hash
|
||||
nix build .#my-package
|
||||
|
||||
# 3. Update hash from error message
|
||||
|
||||
# 4. Test new version
|
||||
nix run .#my-package -- --version
|
||||
|
||||
# 5. Commit
|
||||
git commit -m "chore: update my-package to v2.0.0"
|
||||
```
|
||||
|
||||
### Update Dependencies
|
||||
|
||||
```bash
|
||||
# 1. Update fetcher version/rev
|
||||
vim pkgs/my-package/default.nix
|
||||
|
||||
# 2. Update dependencies if needed
|
||||
vim pkgs/my-package/default.nix
|
||||
|
||||
# 3. Build and test
|
||||
nix build .#my-package
|
||||
nix run .#my-package -- --help
|
||||
|
||||
# 4. Commit
|
||||
git commit -m "fix: update dependencies for my-package"
|
||||
```
|
||||
|
||||
### Add Tests
|
||||
|
||||
```bash
|
||||
# 1. Add test to package
|
||||
vim pkgs/my-package/default.nix
|
||||
|
||||
# 2. Run tests
|
||||
nix build .#my-package
|
||||
|
||||
# 3. Verify tests pass
|
||||
|
||||
# 4. Commit
|
||||
git commit -m "test: add tests for my-package"
|
||||
```
|
||||
|
||||
### Fix Linting Issues
|
||||
|
||||
```bash
|
||||
# 1. Run linter
|
||||
nix develop
|
||||
statix check .
|
||||
|
||||
# 2. Fix issues manually or auto-fix
|
||||
statix fix .
|
||||
|
||||
# 3. Check again
|
||||
statix check .
|
||||
|
||||
# 4. Commit
|
||||
git commit -m "style: fix linting issues"
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Use Caching
|
||||
|
||||
```bash
|
||||
# Use binary cache (if available)
|
||||
nix build .#my-package --substituters https://cache.nixos.org https://your-cache.example.com
|
||||
|
||||
# Use local cache
|
||||
nix build .#my-package --max-jobs 4
|
||||
```
|
||||
|
||||
### Parallel Builds
|
||||
|
||||
```bash
|
||||
# Build multiple packages in parallel
|
||||
nix build .#package1 .#package2 .#package3
|
||||
```
|
||||
|
||||
### Incremental Builds
|
||||
|
||||
```bash
|
||||
# Only rebuild changed packages
|
||||
nix build .#my-package --check
|
||||
|
||||
# Don't rebuild dependencies
|
||||
nix build .#my-package --no-link
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
### Version Bump
|
||||
|
||||
```bash
|
||||
# 1. Update versions as needed
|
||||
vim pkgs/*/default.nix
|
||||
|
||||
# 2. Update CHANGELOG.md
|
||||
vim CHANGELOG.md
|
||||
|
||||
# 3. Test all packages
|
||||
nix flake check
|
||||
|
||||
# 4. Commit
|
||||
git commit -m "chore: prepare release v1.0.0"
|
||||
```
|
||||
|
||||
### Tag Release
|
||||
|
||||
```bash
|
||||
# Create tag
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
|
||||
# Push tag
|
||||
git push origin v1.0.0
|
||||
|
||||
# Push with tags
|
||||
git push --follow-tags
|
||||
```
|
||||
|
||||
### Update Flakes
|
||||
|
||||
Update flake lock after release:
|
||||
|
||||
```bash
|
||||
# Update lock file
|
||||
nix flake update
|
||||
|
||||
# Commit
|
||||
git commit -m "chore: update flake lock"
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
### Before Committing
|
||||
|
||||
- [ ] Code formatted with `nix fmt`
|
||||
- [ ] Passes `statix check .`
|
||||
- [ ] Passes `deadnix .`
|
||||
- [ ] Passes `nix flake check`
|
||||
- [ ] Package builds successfully
|
||||
- [ ] Package runs as expected
|
||||
- [ ] Documentation updated (if needed)
|
||||
- [ ] Commit message follows convention
|
||||
|
||||
### Before Merging PR
|
||||
|
||||
- [ ] All tests pass
|
||||
- [ ] Code review approved
|
||||
- [ ] No merge conflicts
|
||||
- [ ] Documentation complete
|
||||
- [ ] Breaking changes documented
|
||||
|
||||
## Resources
|
||||
|
||||
- [Contributing Guide](../CONTRIBUTING.md) - Code style and guidelines
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding repository structure
|
||||
- [Adding Packages](./adding-packages.md) - Package creation guide
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,402 @@
|
||||
# Getting Started Guide
|
||||
|
||||
Initial setup and basic usage of m3ta-nixpkgs.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure you have Nix installed with flakes enabled:
|
||||
|
||||
```bash
|
||||
# Check Nix version (need 2.4+)
|
||||
nix --version
|
||||
|
||||
# Enable flakes (in /etc/nixos/configuration.nix)
|
||||
nix.settings.experimental-features = ["nix-command" "flakes"]
|
||||
|
||||
# Rebuild NixOS
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
### Adding to Your Flake
|
||||
|
||||
#### Option 1: NixOS Configuration
|
||||
|
||||
Add to your `flake.nix`:
|
||||
|
||||
```nix
|
||||
{
|
||||
description = "My NixOS configuration";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
m3ta-nixpkgs,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations = {
|
||||
myhost = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
./hardware-configuration.nix
|
||||
|
||||
# Import m3ta-nixpkgs modules
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
|
||||
# Apply overlay
|
||||
({pkgs, ...}: {
|
||||
nixpkgs.overlays = [m3ta-nixpkgs.overlays.default];
|
||||
|
||||
# Packages from m3ta-nixpkgs are now available
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
})
|
||||
|
||||
# Home Manager integration
|
||||
home-manager.nixosModules.home-manager
|
||||
{
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
home-manager.users.myusername = {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Option 2: Standalone Home Manager
|
||||
|
||||
```nix
|
||||
{
|
||||
description = "My Home Manager configuration";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
m3ta-nixpkgs,
|
||||
}: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [m3ta-nixpkgs.overlays.default];
|
||||
};
|
||||
in {
|
||||
homeConfigurations.myusername = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
modules = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
{
|
||||
home.username = "myusername";
|
||||
home.homeDirectory = "/home/myusername";
|
||||
home.packages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Usage
|
||||
|
||||
### Using Packages Directly
|
||||
|
||||
Without adding to your configuration:
|
||||
|
||||
```bash
|
||||
# Build a package
|
||||
nix build git+https://code.m3ta.dev/m3tam3re/nixpkgs#code2prompt
|
||||
|
||||
# Run a package
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#zellij-ps
|
||||
|
||||
# List all available packages
|
||||
nix flake show git+https://code.m3ta.dev/m3tam3re/nixpkgs
|
||||
```
|
||||
|
||||
### Using Packages in Configuration
|
||||
|
||||
After applying overlay:
|
||||
|
||||
```nix
|
||||
# System-wide (NixOS)
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
|
||||
# User-only (Home Manager)
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
];
|
||||
```
|
||||
|
||||
### Using Modules
|
||||
|
||||
```nix
|
||||
# Import all modules
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
# Or import specific module
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
|
||||
# Configure module
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Install a Package System-Wide
|
||||
|
||||
```nix
|
||||
# /etc/nixos/configuration.nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
hyprpaper-random
|
||||
];
|
||||
}
|
||||
|
||||
# Apply
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
### Install a Package for Your User
|
||||
|
||||
```nix
|
||||
# home.nix
|
||||
{pkgs, ...}: {
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
zellij-ps
|
||||
];
|
||||
}
|
||||
|
||||
# Apply
|
||||
home-manager switch
|
||||
```
|
||||
|
||||
### Enable a NixOS Module
|
||||
|
||||
```nix
|
||||
# /etc/nixos/configuration.nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
};
|
||||
|
||||
# Apply
|
||||
# sudo nixos-rebuild switch
|
||||
}
|
||||
```
|
||||
|
||||
### Enable a Home Manager Module
|
||||
|
||||
```nix
|
||||
# home.nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
# Apply
|
||||
# home-manager switch
|
||||
}
|
||||
```
|
||||
|
||||
### Use Port Management
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
root /var/www;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Available Packages
|
||||
|
||||
| Package | Description |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| `code2prompt` | Convert code to prompts |
|
||||
| `hyprpaper-random` | Random wallpaper setter for Hyprpaper |
|
||||
| `launch-webapp` | Launch web applications |
|
||||
| `mem0` | AI memory assistant with vector storage |
|
||||
| `msty-studio` | Msty Studio application |
|
||||
| `pomodoro-timer` | Pomodoro timer utility |
|
||||
| `tuxedo-backlight` | Backlight control for Tuxedo laptops |
|
||||
| `zellij-ps` | Project switcher for Zellij |
|
||||
|
||||
## Available Modules
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
- `ports` - Port management across hosts
|
||||
- `mem0` - Mem0 REST API server
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
- `ports` - Port management (with `generateEnvVars`)
|
||||
- `cli.zellij-ps` - Zellij project switcher
|
||||
- `coding.editors` - Editor configurations
|
||||
|
||||
## Development
|
||||
|
||||
### Development Shells
|
||||
|
||||
```bash
|
||||
# Default dev shell
|
||||
nix develop
|
||||
|
||||
# Python dev shell
|
||||
nix develop .#python
|
||||
|
||||
# DevOps dev shell
|
||||
nix develop .#devops
|
||||
```
|
||||
|
||||
### Building and Testing
|
||||
|
||||
```bash
|
||||
# Build package
|
||||
nix build .#code2prompt
|
||||
|
||||
# Validate flake
|
||||
nix flake check
|
||||
|
||||
# List outputs
|
||||
nix flake show
|
||||
|
||||
# Format code
|
||||
nix fmt
|
||||
```
|
||||
|
||||
### Linting (in dev shell)
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
|
||||
# Run linter
|
||||
statix check .
|
||||
|
||||
# Find dead code
|
||||
deadnix .
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Package Not Found
|
||||
|
||||
**Error**: `error: undefined variable 'code2prompt'`
|
||||
|
||||
**Solution**: Make sure you applied the overlay:
|
||||
|
||||
```nix
|
||||
nixpkgs.overlays = [m3ta-nixpkgs.overlays.default];
|
||||
```
|
||||
|
||||
### Module Not Found
|
||||
|
||||
**Error**: `error: The option 'm3ta.mem0' does not exist`
|
||||
|
||||
**Solution**: Make sure you imported the module:
|
||||
|
||||
```nix
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
# or
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
```
|
||||
|
||||
### Hash Mismatch
|
||||
|
||||
**Error**: `got: sha256-AAAAAAAA... expected: sha256-BBBBBB...`
|
||||
|
||||
**Solution**: Copy the `got` hash from the error and update the package definition.
|
||||
|
||||
### Building for Different System
|
||||
|
||||
```bash
|
||||
# Build for aarch64-linux
|
||||
nix build .#code2prompt --system aarch64-linux
|
||||
|
||||
# Build for macOS
|
||||
nix build .#code2prompt --system x86_64-darwin
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Adding Packages](./adding-packages.md) - How to add new packages
|
||||
- [Using Modules](./using-modules.md) - Deep dive into modules
|
||||
- [Port Management](./port-management.md) - Managing service ports
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding the repository structure
|
||||
@@ -0,0 +1,99 @@
|
||||
# Pi Agent Isolation (two-repo setup)
|
||||
|
||||
This guide documents the split setup where:
|
||||
|
||||
- `m3ta-nixpkgs` provides reusable module logic.
|
||||
- `nixos-config` consumes it on specific hosts.
|
||||
|
||||
## 1) In `m3ta-nixpkgs`
|
||||
|
||||
Use:
|
||||
|
||||
- Home Manager module: `coding.agents.pi`
|
||||
- renders Pi config in user space (default path: `.pi/agent` => `~/.pi/agent`)
|
||||
- NixOS module: `m3ta.pi-agent`
|
||||
- dedicated user/group (default `pi-agent`)
|
||||
- state directory (default `/var/lib/pi-agent`)
|
||||
- hardened execution via transient `systemd-run`
|
||||
- host-side wrapper command (default `pi`)
|
||||
- per-user allowlists via `hostUsers.<name>.projectRoots`
|
||||
- host config sync into isolated runtime (default source `.pi/agent`)
|
||||
- managed settings/env merge into isolated runtime
|
||||
|
||||
## 2) In consumer repo (`nixos-config`)
|
||||
|
||||
### Home Manager side
|
||||
|
||||
Keep Pi config rendering enabled for your normal user:
|
||||
|
||||
```nix
|
||||
coding.agents.pi = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
path = ".pi/agent";
|
||||
};
|
||||
```
|
||||
|
||||
### NixOS host side (example: `m3-kratos`)
|
||||
|
||||
Enable isolated wrapper execution:
|
||||
|
||||
```nix
|
||||
m3ta.pi-agent = {
|
||||
enable = true;
|
||||
stateDir = "/var/lib/pi-agent";
|
||||
|
||||
hostUsers = {
|
||||
m3tam3re = {
|
||||
projectRoots = ["~/p" "~/work/private"];
|
||||
# optional; defaults to wrapper.hostConfigPath
|
||||
configPath = ".pi/agent";
|
||||
};
|
||||
};
|
||||
|
||||
settings = {
|
||||
defaultProvider = "anthropic";
|
||||
defaultModel = "anthropic/claude-sonnet-4";
|
||||
quietStartup = true;
|
||||
};
|
||||
|
||||
environment = {
|
||||
PI_TELEMETRY = "0";
|
||||
};
|
||||
|
||||
environmentFiles = [
|
||||
"/run/secrets/pi-agent.env"
|
||||
];
|
||||
|
||||
wrapper = {
|
||||
enable = true;
|
||||
commandName = "pi";
|
||||
hideDirectBinary = true;
|
||||
hostConfigPath = ".pi/agent";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## 3) Authorization model
|
||||
|
||||
The wrapper uses a tightly scoped sudo rule:
|
||||
|
||||
- authorized users may run only the privileged runner command
|
||||
- with `NOPASSWD`
|
||||
- no broad `NOPASSWD: ALL`
|
||||
|
||||
## 4) Merge behavior
|
||||
|
||||
At invocation time, isolated runtime files are built from:
|
||||
|
||||
1. Host user Pi config (synced from source path, e.g. `~/.pi/agent`)
|
||||
2. Nix-managed settings/env (override host values)
|
||||
3. Environment files (appended after managed env attrs)
|
||||
|
||||
This keeps user-authored Pi config available while allowing reproducible Nix overrides.
|
||||
|
||||
## 5) Migration notes
|
||||
|
||||
- If wrapper mode is canonical, remove direct `pi-coding-agent` from user package lists to reduce command-path ambiguity.
|
||||
- Rebuild host config and test from an allowlisted project path.
|
||||
- Validate `pi` process identity runs as `pi-agent`.
|
||||
@@ -0,0 +1,525 @@
|
||||
# Port Management Guide
|
||||
|
||||
Managing service ports across multiple hosts with the `m3ta.ports` module.
|
||||
|
||||
## Overview
|
||||
|
||||
The port management module provides a centralized way to define service ports that can have host-specific overrides. This prevents port conflicts and makes it easy to manage services across multiple machines.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Enable Port Management
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
|
||||
# Define default ports
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
homepage = 8080;
|
||||
};
|
||||
|
||||
# Define host-specific overrides
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
nginx = 8080; # Override on laptop
|
||||
homepage = 3001; # Override on laptop
|
||||
};
|
||||
server = {
|
||||
homepage = 3002; # Override on server
|
||||
};
|
||||
};
|
||||
|
||||
# Set current host (determines which overrides to use)
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Using Ports
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
root /var/www;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `m3ta.ports.enable`
|
||||
|
||||
Enable port management module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.ports.definitions`
|
||||
|
||||
Default port definitions.
|
||||
|
||||
- Type: `attrsOf int`
|
||||
- Default: `{}`
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.hostOverrides`
|
||||
|
||||
Host-specific port overrides.
|
||||
|
||||
- Type: `attrsOf (attrsOf int)`
|
||||
- Default: `{}`
|
||||
|
||||
```nix
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
grafana = 3001;
|
||||
};
|
||||
server = {
|
||||
grafana = 3002;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.currentHost`
|
||||
|
||||
Current hostname. Determines which overrides to apply.
|
||||
|
||||
- Type: `string`
|
||||
- Example: `config.networking.hostName`
|
||||
|
||||
```nix
|
||||
currentHost = "laptop"; # Use laptop overrides
|
||||
```
|
||||
|
||||
### `m3ta.ports.generateEnvVars` (Home Manager only)
|
||||
|
||||
Generate environment variables from ports.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false` (Home Manager)
|
||||
- NixOS: Not available
|
||||
|
||||
When enabled, generates environment variables like:
|
||||
- `PORT_NGINX=8080`
|
||||
- `PORT_GRAFANA=3000`
|
||||
|
||||
## Functions
|
||||
|
||||
### `config.m3ta.ports.get "service"`
|
||||
|
||||
Get port for a service with host-specific override.
|
||||
|
||||
```nix
|
||||
services.nginx = {
|
||||
port = config.m3ta.ports.get "nginx";
|
||||
};
|
||||
```
|
||||
|
||||
If current host is `laptop` and `hostOverrides.laptop.nginx = 8080`, returns `8080`.
|
||||
If no override, returns default `80`.
|
||||
|
||||
### `config.m3ta.ports.getHostPorts "hostname"`
|
||||
|
||||
Get all ports for a specific host.
|
||||
|
||||
```nix
|
||||
# Get all ports for laptop
|
||||
laptopPorts = config.m3ta.ports.getHostPorts "laptop";
|
||||
# Returns: { nginx = 8080; grafana = 3000; ... }
|
||||
```
|
||||
|
||||
### `config.m3ta.ports.listServices`
|
||||
|
||||
List all defined service names.
|
||||
|
||||
```nix
|
||||
allServices = config.m3ta.ports.listServices;
|
||||
# Returns: ["nginx" "grafana" "prometheus" "homepage"]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### NixOS Configuration
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
# Define ports
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
loki = 3100;
|
||||
promtail = 9080;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080;
|
||||
grafana = 3001;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Use ports
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
root /var/www;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
};
|
||||
|
||||
services.prometheus = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "prometheus";
|
||||
};
|
||||
|
||||
services.loki = {
|
||||
enable = true;
|
||||
configuration.http_listen_port = config.m3ta.ports.get "loki";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Home Manager Configuration
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
# Define ports
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
vite = 5173;
|
||||
};
|
||||
hostOverrides.desktop = {
|
||||
vite = 5174;
|
||||
};
|
||||
currentHost = "desktop";
|
||||
generateEnvVars = true; # Generate env vars
|
||||
};
|
||||
|
||||
# Ports are now available as env vars
|
||||
# PORT_DEV_SERVER=3000
|
||||
# PORT_NEXTJS=3001
|
||||
# PORT_VITE=5174
|
||||
|
||||
home.sessionVariables = {
|
||||
DEV_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Custom Modules
|
||||
|
||||
Using ports with custom modules (e.g., `m3ta.mem0`):
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
# Define ports
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
mem0 = 8000;
|
||||
qdrant = 6333;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
mem0 = 8080;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Use with mem0 module
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "mem0"; # 8000 or 8080 on laptop
|
||||
};
|
||||
|
||||
# Use with qdrant service
|
||||
services.qdrant = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "qdrant";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Port File Generation
|
||||
|
||||
Generate a JSON file with all ports:
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
service1 = 80;
|
||||
service2 = 443;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Generate port file
|
||||
environment.etc."m3ta/ports.json".text = builtins.toJSON (
|
||||
config.m3ta.ports.getHostPorts config.networking.hostName
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Conditional Configuration
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
# Only open firewall if binding to non-localhost
|
||||
httpConfig = let
|
||||
port = config.m3ta.ports.get "nginx";
|
||||
in ''
|
||||
server {
|
||||
listen ${toString port};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts =
|
||||
if config.m3ta.ports.get "nginx" == 80
|
||||
then [80]
|
||||
else [];
|
||||
}
|
||||
```
|
||||
|
||||
### Port Ranges
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
service-start = 8000;
|
||||
service-end = 8999;
|
||||
};
|
||||
|
||||
# Use in config
|
||||
services.my-app = {
|
||||
portRange = [
|
||||
config.m3ta.ports.get "service-start"
|
||||
config.m3ta.ports.get "service-end"
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
### Dynamic Port Allocation
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
# Reserve port ranges
|
||||
app-range-start = 9000;
|
||||
app-range-end = 9999;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Calculate next available port
|
||||
services.my-app = {
|
||||
port = config.m3ta.ports.get "app-range-start" + 0;
|
||||
};
|
||||
|
||||
services.my-other-app = {
|
||||
port = config.m3ta.ports.get "app-range-start" + 1;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use Descriptive Service Names
|
||||
|
||||
```nix
|
||||
# Good
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus-ui = 9090;
|
||||
prometheus-push = 9091;
|
||||
};
|
||||
|
||||
# Avoid
|
||||
definitions = {
|
||||
p1 = 80;
|
||||
p2 = 3000;
|
||||
p3 = 9090;
|
||||
};
|
||||
```
|
||||
|
||||
### Group Related Services
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
# Monitoring stack
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
loki = 3100;
|
||||
promtail = 9080;
|
||||
|
||||
# Web services
|
||||
nginx = 80;
|
||||
homepage = 8080;
|
||||
|
||||
# Databases
|
||||
postgres = 5432;
|
||||
redis = 6379;
|
||||
qdrant = 6333;
|
||||
};
|
||||
```
|
||||
|
||||
### Document Overrides
|
||||
|
||||
```nix
|
||||
hostOverrides = {
|
||||
# Laptop: Running multiple dev servers, use higher ports
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
dev-server = 3000;
|
||||
};
|
||||
|
||||
# Server: Production, use standard ports
|
||||
server = {
|
||||
nginx = 80;
|
||||
dev-server = null; # Disable on server
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Handle Missing Ports
|
||||
|
||||
```nix
|
||||
services.some-service = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "some-service" or 8080;
|
||||
};
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Not Found
|
||||
|
||||
Error: `Service "foo" not defined`
|
||||
|
||||
Solution: Add service to `definitions`:
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
foo = 8080;
|
||||
};
|
||||
```
|
||||
|
||||
### Current Host Not Set
|
||||
|
||||
Error: `currentHost not set`
|
||||
|
||||
Solution: Set `currentHost`:
|
||||
|
||||
```nix
|
||||
currentHost = config.networking.hostName;
|
||||
```
|
||||
|
||||
### Port Conflict
|
||||
|
||||
Issue: Two services trying to use same port.
|
||||
|
||||
Solution: Define both in port management:
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
service1 = 8080;
|
||||
service2 = 8081; # Different port
|
||||
};
|
||||
```
|
||||
|
||||
## Migration from Hardcoded Ports
|
||||
|
||||
### Before
|
||||
|
||||
```nix
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen 80;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = 3000;
|
||||
};
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
};
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding the library functions
|
||||
- [Using Modules](./using-modules.md) - Using modules with port management
|
||||
- [Contributing](../CONTRIBUTING.md) - Code style and guidelines
|
||||
@@ -0,0 +1,693 @@
|
||||
# Using Modules Guide
|
||||
|
||||
How to use NixOS and Home Manager modules from m3ta-nixpkgs.
|
||||
|
||||
## Overview
|
||||
|
||||
Modules in m3ta-nixpkgs provide reusable configuration for NixOS (system-level) and Home Manager (user-level) settings. All modules use the `m3ta.*` namespace.
|
||||
|
||||
## Module Organization
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
Located in `modules/nixos/`:
|
||||
|
||||
```
|
||||
modules/nixos/
|
||||
├── default.nix # Aggregates all NixOS modules
|
||||
├── ports.nix # Port management
|
||||
└── mem0.nix # Mem0 REST API server
|
||||
```
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
Located in `modules/home-manager/` with categories:
|
||||
|
||||
```
|
||||
modules/home-manager/
|
||||
├── default.nix # Aggregates all HM modules
|
||||
├── ports.nix # Port management
|
||||
├── cli/ # CLI tools
|
||||
│ ├── default.nix # Aggregates CLI modules
|
||||
│ └── zellij-ps.nix
|
||||
└── coding/ # Development tools
|
||||
├── default.nix # Aggregates coding modules
|
||||
├── editors.nix
|
||||
├── opencode.nix # OpenCode non-agent config
|
||||
└── agents/ # Per-tool agent deployment
|
||||
├── default.nix
|
||||
├── opencode.nix
|
||||
├── claude-code.nix
|
||||
└── pi.nix
|
||||
```
|
||||
|
||||
## Importing Modules
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
#### Import All Modules
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### Import Specific Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### Import from Local Path
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
./modules/nixos/mem0.nix
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
#### Import All Modules
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### Import Specific Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.ports
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### Import Category
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
# Import all CLI modules
|
||||
m3ta-nixpkgs.homeManagerModules.cli.zellij-ps
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Available Modules
|
||||
|
||||
### NixOS Modules
|
||||
|
||||
#### `m3ta.ports`
|
||||
|
||||
Port management across hosts.
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [Port Management Guide](./port-management.md)
|
||||
|
||||
#### `m3ta.mem0`
|
||||
|
||||
Mem0 REST API server for AI memory.
|
||||
|
||||
```nix
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
model = "gpt-4o-mini";
|
||||
};
|
||||
vectorStore = {
|
||||
provider = "qdrant";
|
||||
config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [mem0 Module](../modules/nixos/mem0.md)
|
||||
|
||||
#### `m3ta.pi-agent`
|
||||
|
||||
Isolated Pi execution with a dedicated system user (`pi-agent` by default),
|
||||
a hardened runtime, and a host-side `pi` wrapper command.
|
||||
|
||||
```nix
|
||||
m3ta.pi-agent = {
|
||||
enable = true;
|
||||
stateDir = "/var/lib/pi-agent";
|
||||
|
||||
hostUsers = {
|
||||
m3tam3re = {
|
||||
projectRoots = ["~/p" "~/work/private"];
|
||||
configPath = ".pi/agent"; # optional
|
||||
};
|
||||
};
|
||||
|
||||
settings.defaultModel = "anthropic/claude-sonnet-4";
|
||||
environment.PI_TELEMETRY = "0";
|
||||
wrapper.commandName = "pi";
|
||||
wrapper.hideDirectBinary = true;
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [Pi Agent Isolation Guide](./pi-agent-isolation.md)
|
||||
|
||||
### Home Manager Modules
|
||||
|
||||
#### `m3ta.ports`
|
||||
|
||||
Port management (similar to NixOS, with `generateEnvVars`).
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
generateEnvVars = true;
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [Port Management Guide](./port-management.md)
|
||||
|
||||
#### `cli.zellij-ps`
|
||||
|
||||
Zellij project switcher for quickly navigating between project folders.
|
||||
|
||||
```nix
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
package = pkgs.zellij-ps;
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [zellij-ps Module](../modules/home-manager/cli/zellij-ps.md)
|
||||
|
||||
#### `coding.editors`
|
||||
|
||||
Editor configurations.
|
||||
|
||||
```nix
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
zed.enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
**Documentation**: [Editors Module](../modules/home-manager/coding/editors.md)
|
||||
|
||||
### `coding.opencode`
|
||||
|
||||
OpenCode AI coding assistant (non-agent config: theme, formatter, plugins).
|
||||
|
||||
```nix
|
||||
coding.opencode = {
|
||||
enable = true;
|
||||
ohMyOpencodeSettings = {
|
||||
agents.sisyphus.model = "anthropic/claude-opus-4-5";
|
||||
};
|
||||
extraSettings = {
|
||||
provider.anthropic.name = "Anthropic";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `coding.agents.opencode`
|
||||
|
||||
OpenCode agent deployment from canonical TOML definitions.
|
||||
|
||||
```nix
|
||||
coding.agents.opencode = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
modelOverrides = {
|
||||
chiron = "anthropic/claude-sonnet-4";
|
||||
};
|
||||
externalSkills = [
|
||||
{ src = inputs.skills-anthropic; }
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
### `coding.agents.claude-code`
|
||||
|
||||
Claude Code agent deployment from canonical TOML definitions.
|
||||
|
||||
```nix
|
||||
coding.agents.claude-code = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
};
|
||||
```
|
||||
|
||||
### `coding.agents.pi`
|
||||
|
||||
Pi agent deployment from canonical TOML definitions.
|
||||
|
||||
```nix
|
||||
coding.agents.pi = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
path = ".pi/agent"; # default; can be changed
|
||||
};
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Module Configuration
|
||||
|
||||
All modules follow this pattern:
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "description";
|
||||
# ... other options
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# ... configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Configuration
|
||||
|
||||
Use `mkIf` to conditionally apply config:
|
||||
|
||||
```nix
|
||||
config = mkIf cfg.enable {
|
||||
# Only applied when cfg.enable = true
|
||||
services.my-service = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Conditions
|
||||
|
||||
Use `mkMerge` for multiple conditions:
|
||||
|
||||
```nix
|
||||
config = mkMerge [
|
||||
(mkIf cfg.feature1.enable {
|
||||
# Applied when feature1 is enabled
|
||||
})
|
||||
(mkIf cfg.feature2.enable {
|
||||
# Applied when feature2 is enabled
|
||||
})
|
||||
];
|
||||
```
|
||||
|
||||
### Optional Dependencies
|
||||
|
||||
```nix
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.defaultPackage;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.my-service = {
|
||||
package = cfg.package;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Minimal NixOS Configuration
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
my-service = 8080;
|
||||
};
|
||||
currentHost = "laptop";
|
||||
};
|
||||
|
||||
services.my-custom-service = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "my-service";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Full NixOS Configuration
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
# Port management
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
mem0 = 8000;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080;
|
||||
mem0 = 8081;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Mem0 service
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "mem0";
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
};
|
||||
vectorStore = {
|
||||
provider = "qdrant";
|
||||
config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Nginx
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
root /var/www;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
# Grafana
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Minimal Home Manager Configuration
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
currentHost = "desktop";
|
||||
};
|
||||
|
||||
home.sessionVariables = {
|
||||
DEV_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Full Home Manager Configuration
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
# Port management
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
vite = 5173;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
vite = 5174;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# CLI tools
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
# Coding tools
|
||||
coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
};
|
||||
|
||||
# Packages
|
||||
home.packages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
|
||||
# Environment variables
|
||||
home.sessionVariables = {
|
||||
EDITOR = "nvim";
|
||||
DEV_SERVER_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options Reference
|
||||
|
||||
### Standard Options
|
||||
|
||||
All modules typically include:
|
||||
|
||||
| Option | Type | Description |
|
||||
|---------|-------|-------------|
|
||||
| `enable` | `boolean` | Enable the module |
|
||||
| `package` | `package` | Custom package to use |
|
||||
| `extraConfig` | `attrs` | Additional configuration |
|
||||
|
||||
### Port Management Options
|
||||
|
||||
| Option | Type | Description |
|
||||
|---------|-------|-------------|
|
||||
| `definitions` | `attrsOf int` | Default port definitions |
|
||||
| `hostOverrides` | `attrsOf attrs` | Host-specific overrides |
|
||||
| `currentHost` | `string` | Current hostname |
|
||||
| `generateEnvVars` | `boolean` | Generate environment variables (HM only) |
|
||||
|
||||
## Combining with Other Flakes
|
||||
|
||||
### Using Multiple Module Sources
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
# m3ta-nixpkgs modules
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
|
||||
# Other flake modules
|
||||
inputs.impermanence.nixosModules.impermanence
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
];
|
||||
|
||||
# Configure all modules
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {nginx = 80;};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
environment.persistence = {
|
||||
"/persist" = {
|
||||
directories = ["/var/lib/mem0"];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Using with Secrets
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
];
|
||||
|
||||
sops.secrets = {
|
||||
openai-api-key = {};
|
||||
};
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
llm = {
|
||||
apiKeyFile = config.sops.secrets.openai-api-key.path;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Module Not Found
|
||||
|
||||
Error: `error: The option 'm3ta.mymodule' does not exist`
|
||||
|
||||
**Solutions**:
|
||||
1. Make sure you imported the module:
|
||||
|
||||
```nix
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
```
|
||||
|
||||
2. Check module name is correct
|
||||
|
||||
```nix
|
||||
# Correct
|
||||
m3ta.mem0.enable = true;
|
||||
|
||||
# Wrong
|
||||
m3ta.mymodule.enable = true; # Doesn't exist
|
||||
```
|
||||
|
||||
### Option Type Mismatch
|
||||
|
||||
Error: `type mismatch at 'm3ta.mymodule.enable', expected a boolean but got a list`
|
||||
|
||||
**Solution**: Check option types in documentation
|
||||
|
||||
```nix
|
||||
# Correct
|
||||
m3ta.mymodule.enable = true;
|
||||
|
||||
# Wrong
|
||||
m3ta.mymodule.enable = [true]; # Should be boolean
|
||||
```
|
||||
|
||||
### Port Not Defined
|
||||
|
||||
Error: `Service "foo" not defined`
|
||||
|
||||
**Solution**: Add to port definitions
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
definitions = {
|
||||
foo = 8080; # Add this
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use Namespaces
|
||||
|
||||
Always use the `m3ta.*` namespace:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
m3ta.mem0.enable = true;
|
||||
|
||||
# Bad (potential conflicts)
|
||||
mem0.enable = true;
|
||||
```
|
||||
|
||||
### Document Your Configuration
|
||||
|
||||
Add comments explaining module usage:
|
||||
|
||||
```nix
|
||||
# Port management for multi-host setup
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
hostOverrides.laptop = {
|
||||
nginx = 8080; # Use different port on laptop
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Mem0 AI memory service
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "mem0";
|
||||
};
|
||||
```
|
||||
|
||||
### Test Configuration
|
||||
|
||||
```bash
|
||||
# Test NixOS configuration without applying
|
||||
sudo nixos-rebuild test --flake .#hostname
|
||||
|
||||
# Check configuration
|
||||
nix flake check
|
||||
|
||||
# Show all options
|
||||
nix eval .#nixosConfigurations.hostname.config.m3ta --apply builtins.attrNames
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Port Management](./port-management.md) - Detailed port management guide
|
||||
- [Adding Packages](./adding-packages.md) - How to add new packages
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding module structure
|
||||
- [Contributing](../CONTRIBUTING.md) - Code style and guidelines
|
||||
@@ -0,0 +1,219 @@
|
||||
# rofi-project-opener Module
|
||||
|
||||
Home Manager module for configuring the rofi-project-opener package.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides declarative configuration for rofi-project-opener, a Rofi-based project launcher. It generates the necessary configuration files and installs the package.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
cli.rofi-project-opener = {
|
||||
enable = true;
|
||||
projectDirs = {
|
||||
dev = { path = "~/dev"; };
|
||||
work = { path = "~/work"; args = "--agent work-assistant"; };
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### `cli.rofi-project-opener.enable`
|
||||
|
||||
Whether to enable rofi-project-opener.
|
||||
|
||||
**Type:** `boolean`
|
||||
**Default:** `false`
|
||||
|
||||
### `cli.rofi-project-opener.projectDirs`
|
||||
|
||||
Attribute set of base directories to scan for project subdirectories.
|
||||
|
||||
**Type:** `attrsOf (submodule)`
|
||||
|
||||
Each entry is a submodule with:
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `path` | `str` | required | Base directory path (supports `~`) |
|
||||
| `args` | `str` | `""` | Arguments to pass to command |
|
||||
|
||||
**Default:**
|
||||
```nix
|
||||
{
|
||||
dev = { path = "~/dev"; };
|
||||
projects = { path = "~/projects"; };
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```nix
|
||||
projectDirs = {
|
||||
nixpkgs = { path = "~/p/NIX"; args = "--agent nix-expert"; };
|
||||
chat = { path = "~/p/CHAT"; args = "--agent chiron"; };
|
||||
dev = { path = "~/dev"; };
|
||||
work = { path = "~/work"; args = "--profile work"; };
|
||||
};
|
||||
```
|
||||
|
||||
### `cli.rofi-project-opener.terminal`
|
||||
|
||||
Terminal emulator to use for launching projects.
|
||||
|
||||
**Type:** `either str package`
|
||||
**Default:** `"kitty"`
|
||||
|
||||
**Examples:**
|
||||
```nix
|
||||
# Using a package
|
||||
terminal = pkgs.kitty;
|
||||
terminal = pkgs.alacritty;
|
||||
|
||||
# Using a string (must be in PATH)
|
||||
terminal = "kitty";
|
||||
terminal = "wezterm";
|
||||
```
|
||||
|
||||
### `cli.rofi-project-opener.terminalCommand`
|
||||
|
||||
Command to run in the terminal after navigating to the project directory.
|
||||
|
||||
**Type:** `str`
|
||||
**Default:** `""` (runs `opencode` with args)
|
||||
|
||||
**Placeholders:**
|
||||
- `%s` - Project path
|
||||
- `%a` - Project args (from `projectDirs.<name>.args`)
|
||||
|
||||
**Examples:**
|
||||
```nix
|
||||
# Default behavior - run opencode with project args
|
||||
terminalCommand = "";
|
||||
|
||||
# Explicit opencode with args
|
||||
terminalCommand = "opencode %a";
|
||||
|
||||
# Different editor
|
||||
terminalCommand = "nvim";
|
||||
|
||||
# VSCode with path
|
||||
terminalCommand = "code %s";
|
||||
|
||||
# Custom application
|
||||
terminalCommand = "my-dev-tool --project %s %a";
|
||||
```
|
||||
|
||||
### `cli.rofi-project-opener.rofiPrompt`
|
||||
|
||||
Prompt text displayed in the Rofi menu.
|
||||
|
||||
**Type:** `str`
|
||||
**Default:** `"Select project"`
|
||||
|
||||
### `cli.rofi-project-opener.rofiArgs`
|
||||
|
||||
Arguments to pass to Rofi.
|
||||
|
||||
**Type:** `listOf str`
|
||||
**Default:** `["-dmenu" "-i"]`
|
||||
|
||||
**Example:**
|
||||
```nix
|
||||
rofiArgs = ["-dmenu" "-i" "-theme" "gruvbox" "-width" "50"];
|
||||
```
|
||||
|
||||
## Generated Files
|
||||
|
||||
The module generates these configuration files:
|
||||
|
||||
### `~/.config/rofi-project-opener/projects.json`
|
||||
|
||||
JSON file containing project directories:
|
||||
|
||||
```json
|
||||
{
|
||||
"dev": {"path": "~/dev", "args": ""},
|
||||
"work": {"path": "~/work", "args": "--agent work-assistant"}
|
||||
}
|
||||
```
|
||||
|
||||
### `~/.config/rofi-project-opener/config`
|
||||
|
||||
Shell configuration file:
|
||||
|
||||
```bash
|
||||
TERMINAL="/nix/store/.../bin/kitty"
|
||||
TERMINAL_CMD="opencode %a"
|
||||
ROFI_PROMPT="Select project"
|
||||
ROFI_ARGS="-dmenu -i"
|
||||
```
|
||||
|
||||
## Full Example
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
cli.rofi-project-opener = {
|
||||
enable = true;
|
||||
|
||||
# Project directories with optional args
|
||||
projectDirs = {
|
||||
nixpkgs = {
|
||||
path = "~/p/NIX";
|
||||
args = "";
|
||||
};
|
||||
chat = {
|
||||
path = "~/p/CHAT";
|
||||
args = "--agent chiron";
|
||||
};
|
||||
dev = {
|
||||
path = "~/dev";
|
||||
args = "";
|
||||
};
|
||||
};
|
||||
|
||||
# Terminal configuration
|
||||
terminal = pkgs.kitty;
|
||||
terminalCommand = "opencode %a";
|
||||
|
||||
# Rofi configuration
|
||||
rofiPrompt = "Open Project";
|
||||
rofiArgs = ["-dmenu" "-i" "-theme" "nord"];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After enabling, run:
|
||||
|
||||
```bash
|
||||
rofi-project-opener
|
||||
```
|
||||
|
||||
Or bind to a keyboard shortcut in your window manager:
|
||||
|
||||
**Hyprland:**
|
||||
```nix
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod, P, exec, rofi-project-opener"
|
||||
];
|
||||
```
|
||||
|
||||
**Sway:**
|
||||
```nix
|
||||
wayland.windowManager.sway.config.keybindings = {
|
||||
"${modifier}+p" = "exec rofi-project-opener";
|
||||
};
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [rofi-project-opener Package](../../../packages/rofi-project-opener.md) - Package documentation
|
||||
- [zellij-ps Module](./zellij-ps.md) - Similar project switcher for Zellij
|
||||
- [Home Manager Overview](../overview.md) - All Home Manager modules
|
||||
@@ -0,0 +1,312 @@
|
||||
# stt-ptt Home Manager Module
|
||||
|
||||
Push to Talk Speech to Text for Home Manager.
|
||||
|
||||
## Overview
|
||||
|
||||
This module configures stt-ptt, a push-to-talk speech-to-text tool using whisper.cpp. It handles model downloads, environment configuration, and package installation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This will:
|
||||
- Install stt-ptt with default whisper-cpp
|
||||
- Download the `ggml-large-v3-turbo` model on first activation
|
||||
- Set environment variables for model path and notification timeout
|
||||
|
||||
## Module Options
|
||||
|
||||
### `cli.stt-ptt.enable`
|
||||
|
||||
Enable the stt-ptt module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `cli.stt-ptt.whisperPackage`
|
||||
|
||||
The whisper-cpp package to use for transcription.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.whisper-cpp`
|
||||
|
||||
**Pre-built variants:**
|
||||
|
||||
```nix
|
||||
# CPU (default)
|
||||
whisperPackage = pkgs.whisper-cpp;
|
||||
|
||||
# Vulkan GPU acceleration (pre-built)
|
||||
whisperPackage = pkgs.whisper-cpp-vulkan;
|
||||
```
|
||||
|
||||
**Override options** (can be combined):
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `cudaSupport` | NVIDIA CUDA acceleration |
|
||||
| `rocmSupport` | AMD ROCm acceleration |
|
||||
| `vulkanSupport` | Vulkan GPU acceleration |
|
||||
| `coreMLSupport` | Apple CoreML (macOS only) |
|
||||
| `metalSupport` | Apple Metal (macOS ARM only) |
|
||||
|
||||
```nix
|
||||
# NVIDIA CUDA support
|
||||
whisperPackage = pkgs.whisper-cpp.override { cudaSupport = true; };
|
||||
|
||||
# AMD ROCm support
|
||||
whisperPackage = pkgs.whisper-cpp.override { rocmSupport = true; };
|
||||
|
||||
# Vulkan support (manual override)
|
||||
whisperPackage = pkgs.whisper-cpp.override { vulkanSupport = true; };
|
||||
```
|
||||
|
||||
### `cli.stt-ptt.model`
|
||||
|
||||
The Whisper model to use. Models are automatically downloaded from HuggingFace on first activation.
|
||||
|
||||
- Type: `string`
|
||||
- Default: `"ggml-large-v3-turbo"`
|
||||
|
||||
Available models (sorted by size):
|
||||
|
||||
| Model | Size | Notes |
|
||||
|-------|------|-------|
|
||||
| `ggml-tiny` | 75MB | Fastest, lowest quality |
|
||||
| `ggml-tiny.en` | 75MB | English-only, slightly faster |
|
||||
| `ggml-base` | 142MB | Fast, basic quality |
|
||||
| `ggml-base.en` | 142MB | English-only |
|
||||
| `ggml-small` | 466MB | Balanced speed/quality |
|
||||
| `ggml-small.en` | 466MB | English-only |
|
||||
| `ggml-medium` | 1.5GB | Good quality |
|
||||
| `ggml-medium.en` | 1.5GB | English-only |
|
||||
| `ggml-large-v1` | 2.9GB | High quality (original) |
|
||||
| `ggml-large-v2` | 2.9GB | High quality (improved) |
|
||||
| `ggml-large-v3` | 2.9GB | Highest quality |
|
||||
| `ggml-large-v3-turbo` | 1.6GB | High quality, optimized speed (recommended) |
|
||||
|
||||
Quantized versions (`q5_0`, `q5_1`, `q8_0`) are also available for reduced size.
|
||||
|
||||
### `cli.stt-ptt.notifyTimeout`
|
||||
|
||||
Notification timeout in milliseconds for the recording indicator.
|
||||
|
||||
- Type: `integer`
|
||||
- Default: `3000`
|
||||
- Example: `5000` (5 seconds), `0` (persistent)
|
||||
|
||||
### `cli.stt-ptt.language`
|
||||
|
||||
Language for speech recognition. Use "auto" for automatic language detection, or specify a language code for better accuracy.
|
||||
|
||||
- Type: `enum ["auto", "en", "es", "fr", "de", "it", "pt", "ru", "zh", "ja", "ko", "ar", "hi", "tr", "pl", "nl", "sv", "da", "fi", "no", "vi", "th", "id", "uk", "cs"]`
|
||||
- Default: `"auto"`
|
||||
|
||||
**Auto-detection**: When set to "auto", whisper.cpp analyzes the audio to determine the spoken language automatically.
|
||||
|
||||
**Language specification**: Specifying a language code improves transcription accuracy if you know the language in advance.
|
||||
|
||||
```nix
|
||||
# Automatic language detection (default)
|
||||
language = "auto";
|
||||
|
||||
# Force English transcription
|
||||
language = "en";
|
||||
|
||||
# Spanish transcription
|
||||
language = "es";
|
||||
```
|
||||
|
||||
**Common language codes:**
|
||||
|
||||
| Code | Language |
|
||||
|------|----------|
|
||||
| `en` | English |
|
||||
| `es` | Spanish |
|
||||
| `fr` | French |
|
||||
| `de` | German |
|
||||
| `zh` | Chinese |
|
||||
| `ja` | Japanese |
|
||||
| `ko` | Korean |
|
||||
|
||||
whisper.cpp supports 100+ languages. See whisper.cpp documentation for the full list.
|
||||
|
||||
## Usage
|
||||
|
||||
After enabling, bind `stt-ptt start` and `stt-ptt stop` to a key:
|
||||
|
||||
```bash
|
||||
# Start recording
|
||||
stt-ptt start
|
||||
|
||||
# Stop recording and transcribe (types result)
|
||||
stt-ptt stop
|
||||
```
|
||||
|
||||
### Keybinding Examples
|
||||
|
||||
#### Hyprland
|
||||
|
||||
```nix
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
bind = [
|
||||
"SUPER, V, exec, stt-ptt start"
|
||||
];
|
||||
bindr = [
|
||||
"SUPER, V, exec, stt-ptt stop"
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
Or in `hyprland.conf`:
|
||||
|
||||
```conf
|
||||
# Press to start recording, release to transcribe
|
||||
bind = SUPER, V, exec, stt-ptt start
|
||||
bindr = SUPER, V, exec, stt-ptt stop
|
||||
```
|
||||
|
||||
#### Sway
|
||||
|
||||
```conf
|
||||
bindsym --no-repeat $mod+v exec stt-ptt start
|
||||
bindsym --release $mod+v exec stt-ptt stop
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
### Fast English Transcription
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-base.en";
|
||||
notifyTimeout = 2000;
|
||||
};
|
||||
```
|
||||
|
||||
### Language-Specific Transcription
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-large-v3-turbo";
|
||||
language = "es"; # Force Spanish transcription
|
||||
};
|
||||
```
|
||||
|
||||
### High Quality with NVIDIA GPU
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-large-v3";
|
||||
whisperPackage = pkgs.whisper-cpp.override { cudaSupport = true; };
|
||||
};
|
||||
```
|
||||
|
||||
### Vulkan GPU Acceleration
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-large-v3-turbo";
|
||||
whisperPackage = pkgs.whisper-cpp-vulkan;
|
||||
};
|
||||
```
|
||||
|
||||
### AMD GPU with ROCm
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-large-v3-turbo";
|
||||
whisperPackage = pkgs.whisper-cpp.override { rocmSupport = true; };
|
||||
};
|
||||
```
|
||||
|
||||
### Balanced Setup
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-small";
|
||||
notifyTimeout = 3000;
|
||||
};
|
||||
```
|
||||
|
||||
## File Locations
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `~/.local/share/stt-ptt/models/` | Downloaded Whisper models |
|
||||
| `~/.cache/stt-ptt/stt.wav` | Temporary audio recording |
|
||||
| `~/.cache/stt-ptt/stt.pid` | PID file for recording process |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The module sets these automatically:
|
||||
|
||||
| Variable | Value |
|
||||
|----------|-------|
|
||||
| `STT_MODEL` | `~/.local/share/stt-ptt/models/<model>.bin` |
|
||||
| `STT_LANGUAGE` | Configured language ("auto" by default) |
|
||||
| `STT_NOTIFY_TIMEOUT` | Configured timeout in ms |
|
||||
|
||||
## Requirements
|
||||
|
||||
- Wayland compositor (wtype is Wayland-only)
|
||||
- PipeWire for audio recording
|
||||
- Desktop notification daemon
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Model Download Failed
|
||||
|
||||
The model downloads on first `home-manager switch`. If it fails:
|
||||
|
||||
```bash
|
||||
# Manual download
|
||||
mkdir -p ~/.local/share/stt-ptt/models
|
||||
curl -L -o ~/.local/share/stt-ptt/models/ggml-large-v3-turbo.bin \
|
||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
|
||||
```
|
||||
|
||||
### Transcription Too Slow
|
||||
|
||||
Use a smaller model or enable GPU acceleration:
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-tiny.en"; # Much faster
|
||||
};
|
||||
```
|
||||
|
||||
### Text Not Appearing
|
||||
|
||||
1. Ensure you're on Wayland: `echo $XDG_SESSION_TYPE`
|
||||
2. Check if wtype works: `wtype "test"`
|
||||
3. Some apps may need focus; try clicking the text field first
|
||||
|
||||
## Related
|
||||
|
||||
- [stt-ptt Package](../../../packages/stt-ptt.md) - Package documentation
|
||||
- [Using Modules Guide](../../../guides/using-modules.md) - Module usage patterns
|
||||
@@ -0,0 +1,81 @@
|
||||
# zellij-ps Home Manager Module
|
||||
|
||||
Zellij project switcher for Home Manager.
|
||||
|
||||
## Overview
|
||||
|
||||
This module configures the zellij-ps tool, a Fish script that provides a fast, interactive way to switch between project folders in Zellij terminal multiplexer sessions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `cli.zellij-ps.enable`
|
||||
|
||||
Enable zellij-ps module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `cli.zellij-ps.package`
|
||||
|
||||
Custom package to use.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.zellij-ps`
|
||||
|
||||
## Usage
|
||||
|
||||
After enabling, zellij-ps will be available in your path:
|
||||
|
||||
```bash
|
||||
# Run from outside Zellij to start a project session
|
||||
zellij-ps
|
||||
|
||||
# Or pass a project path directly
|
||||
zellij-ps ~/projects/my-project
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
1. Set `$PROJECT_FOLDERS` in your shell config (e.g., `~/projects:~/code`)
|
||||
2. Run `zellij-ps` from outside a Zellij session
|
||||
3. Use fzf to select a project from your configured folders
|
||||
4. Zellij will create or attach to a session for that project
|
||||
|
||||
## Configuration
|
||||
|
||||
### Custom Package
|
||||
|
||||
Use a custom or modified package:
|
||||
|
||||
```nix
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
package = pkgs.callPackage ./my-zellij-ps {};
|
||||
};
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
The module ensures these are installed:
|
||||
|
||||
- `fish` - Shell for script execution
|
||||
- `fd` - Fast file search
|
||||
- `fzf` - Fuzzy finder
|
||||
- `zellij` - Terminal multiplexer
|
||||
|
||||
## Related
|
||||
|
||||
- [zellij-ps Package](../../packages/zellij-ps.md) - Package documentation
|
||||
- [Using Modules Guide](../../guides/using-modules.md) - Module usage patterns
|
||||
@@ -0,0 +1,317 @@
|
||||
# editors Home Manager Module
|
||||
|
||||
Editor configurations for Home Manager.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides pre-configured settings for various code editors, making it easy to set up your development environment.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
zed.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `m3ta.coding.editors.enable`
|
||||
|
||||
Enable the editors module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.coding.editors.neovim.enable`
|
||||
|
||||
Enable Neovim configuration.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.coding.editors.neovim.package`
|
||||
|
||||
Custom Neovim package.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.neovim`
|
||||
|
||||
### `m3ta.coding.editors.zed.enable`
|
||||
|
||||
Enable Zed editor configuration.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.coding.editors.zed.package`
|
||||
|
||||
Custom Zed package.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.zed`
|
||||
|
||||
## Supported Editors
|
||||
|
||||
### Neovim
|
||||
|
||||
Neovim is a highly extensible Vim-based text editor.
|
||||
|
||||
```nix
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim = {
|
||||
enable = true;
|
||||
package = pkgs.neovim;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Features**:
|
||||
- Vim-style editing
|
||||
- Plugin system
|
||||
- Lua scripting
|
||||
- Fast performance
|
||||
- Built-in LSP support
|
||||
|
||||
**Configuration**: The module provides sensible defaults. You can customize by adding your own configuration.
|
||||
|
||||
### Zed
|
||||
|
||||
Zed is a high-performance, multiplayer code editor.
|
||||
|
||||
```nix
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
zed = {
|
||||
enable = true;
|
||||
package = pkgs.zed;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Features**:
|
||||
- Fast startup
|
||||
- Built-in collaboration
|
||||
- AI assistance (optional)
|
||||
- Modern UI
|
||||
- Low memory usage
|
||||
|
||||
**Configuration**: Zed uses JSON configuration files that can be customized.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Minimal Neovim Setup
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Minimal Zed Setup
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
zed.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Editors
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
zed.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Package
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.coding.editors = {
|
||||
enable = true;
|
||||
neovim = {
|
||||
enable = true;
|
||||
package = pkgs.neovim-unwrapped; # Use unwrapped version
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Neovim
|
||||
|
||||
The module sets up Neovim configuration in:
|
||||
|
||||
```
|
||||
~/.config/nvim/
|
||||
```
|
||||
|
||||
You can extend it with:
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
xdg.configFile."nvim/init.lua".text = ''
|
||||
-- Your custom Neovim configuration
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
### Zed
|
||||
|
||||
Zed configuration is in:
|
||||
|
||||
```
|
||||
~/.config/zed/settings.json
|
||||
```
|
||||
|
||||
You can customize it with:
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
xdg.configFile."zed/settings.json".text = builtins.toJSON {
|
||||
# Your custom Zed settings
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Manual Configuration
|
||||
|
||||
If you have existing editor configurations, you can:
|
||||
|
||||
1. Backup your current config
|
||||
2. Enable the module
|
||||
3. Test it out
|
||||
4. Gradually migrate custom settings
|
||||
|
||||
**Backup**:
|
||||
|
||||
```bash
|
||||
# Backup Neovim
|
||||
cp -r ~/.config/nvim ~/.config/nvim.backup
|
||||
|
||||
# Backup Zed
|
||||
cp -r ~/.config/zed ~/.config/zed.backup
|
||||
```
|
||||
|
||||
### From Other Editors
|
||||
|
||||
**Vim to Neovim**:
|
||||
- Most Vim configurations work with Neovim
|
||||
- Enable module and test
|
||||
- Migrate plugins to modern Lua versions
|
||||
|
||||
**VSCode to Zed**:
|
||||
- Zed has built-in keybinding presets
|
||||
- Enable module and check keybindings
|
||||
- Adjust as needed
|
||||
|
||||
## Keybindings
|
||||
|
||||
### Neovim
|
||||
|
||||
Default keybindings (Vim-style):
|
||||
|
||||
| Mode | Key | Action |
|
||||
|-------|------|---------|
|
||||
| Normal | `i` | Enter insert mode |
|
||||
| Normal | `ESC` | Exit insert mode |
|
||||
| Normal | `:w` | Save |
|
||||
| Normal | `:q` | Quit |
|
||||
| Normal | `u` | Undo |
|
||||
| Normal | `Ctrl+r` | Redo |
|
||||
|
||||
### Zed
|
||||
|
||||
Default keybindings:
|
||||
|
||||
| Mode | Key | Action |
|
||||
|-------|------|---------|
|
||||
| General | `Ctrl+S` | Save |
|
||||
| General | `Ctrl+P` | Command palette |
|
||||
| General | `Ctrl+Shift+P` | File palette |
|
||||
| Navigation | `Ctrl+B` | Toggle sidebar |
|
||||
| Navigation | `Ctrl+Shift+B` | Toggle activity bar |
|
||||
|
||||
## Plugins and Extensions
|
||||
|
||||
### Neovim
|
||||
|
||||
The module provides a base. You can add plugins using:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
programs.neovim.plugins = with pkgs.vimPlugins; [
|
||||
nvim-lspconfig
|
||||
nvim-treesitter
|
||||
telescope-nvim
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Zed
|
||||
|
||||
Zed extensions are managed through the editor:
|
||||
|
||||
1. Open Zed
|
||||
2. Go to Extensions (Ctrl+Shift+X)
|
||||
3. Browse and install extensions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Editor Not Found
|
||||
|
||||
Ensure the editor package is installed:
|
||||
|
||||
```bash
|
||||
# Check Neovim
|
||||
which nvim
|
||||
|
||||
# Check Zed
|
||||
which zed
|
||||
```
|
||||
|
||||
### Configuration Not Applied
|
||||
|
||||
Check if the module is enabled:
|
||||
|
||||
```bash
|
||||
# Check Home Manager state
|
||||
home-manager show
|
||||
|
||||
# View current config
|
||||
nix eval .#homeConfigurations.username.config.m3ta.coding.editors --apply builtins.attrNames
|
||||
```
|
||||
|
||||
### Conflicts with Existing Config
|
||||
|
||||
If you have existing configuration:
|
||||
|
||||
1. Backup current config
|
||||
2. Test module with fresh config
|
||||
3. Gradually add custom settings
|
||||
|
||||
## Related
|
||||
|
||||
- [Using Modules Guide](../../guides/using-modules.md) - How to use modules
|
||||
- [Adding Packages](../../guides/adding-packages.md) - How to add new packages
|
||||
@@ -0,0 +1,206 @@
|
||||
# Home Manager Modules Overview
|
||||
|
||||
Overview of available Home Manager modules in m3ta-nixpkgs.
|
||||
|
||||
## Available Modules
|
||||
|
||||
### Core Modules
|
||||
|
||||
- [ports](./ports.md) - Port management across hosts
|
||||
|
||||
### CLI Modules (`cli/`)
|
||||
|
||||
- [zellij-ps](./cli/zellij-ps.md) - Zellij project switcher
|
||||
|
||||
### Coding Modules (`coding/`)
|
||||
|
||||
- [editors](./coding/editors.md) - Editor configurations
|
||||
|
||||
## Importing Modules
|
||||
|
||||
### Import All Modules
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Import Specific Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.ports
|
||||
m3ta-nixpkgs.homeManagerModules.zellij-ps
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Import Category
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.cli.zellij-ps
|
||||
m3ta-nixpkgs.homeManagerModules.coding.editors
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Module Namespace
|
||||
|
||||
All Home Manager modules use the `m3ta.*` namespace:
|
||||
|
||||
```nix
|
||||
# Port management
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {dev-server = 3000;};
|
||||
};
|
||||
|
||||
# CLI tools
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
# Coding tools
|
||||
coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
## Module Categories
|
||||
|
||||
### Core
|
||||
|
||||
Essential modules for all users:
|
||||
|
||||
- **ports** - Port management with optional environment variable generation
|
||||
|
||||
### CLI (`cli/`)
|
||||
|
||||
Command-line interface tools and utilities:
|
||||
|
||||
- **zellij-ps** - Project switcher for Zellij terminal multiplexer
|
||||
|
||||
### Coding (`coding/`)
|
||||
|
||||
Development tools and configurations:
|
||||
|
||||
- **editors** - Editor configurations (Neovim, Zed, etc.)
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### With NixOS
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
# NixOS modules
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
# Home Manager integration
|
||||
home-manager.users.myusername = {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Home Manager
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
currentHost = "desktop";
|
||||
};
|
||||
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
coding.editors = {
|
||||
enable = true;
|
||||
neovim.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Locations
|
||||
|
||||
### Core
|
||||
|
||||
- `modules/home-manager/ports.nix` - Port management module
|
||||
|
||||
### CLI
|
||||
|
||||
- `modules/home-manager/cli/default.nix` - CLI module aggregator
|
||||
- `modules/home-manager/cli/zellij-ps.nix` - Zellij project switcher
|
||||
|
||||
### Coding
|
||||
|
||||
- `modules/home-manager/coding/default.nix` - Coding module aggregator
|
||||
- `modules/home-manager/coding/editors.nix` - Editor configurations
|
||||
|
||||
## Adding New Modules
|
||||
|
||||
### Core Module
|
||||
|
||||
1. Create: `modules/home-manager/my-module.nix`
|
||||
2. Add to `modules/home-manager/default.nix`
|
||||
|
||||
### CLI Module
|
||||
|
||||
1. Create: `modules/home-manager/cli/my-tool.nix`
|
||||
2. Add to `modules/home-manager/cli/default.nix`
|
||||
|
||||
### Coding Module
|
||||
|
||||
1. Create: `modules/home-manager/coding/my-tool.nix`
|
||||
2. Add to `modules/home-manager/coding/default.nix`
|
||||
|
||||
### Module Template
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.category.myModule;
|
||||
in {
|
||||
options.m3ta.category.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
# ... options
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Using Modules Guide](../../guides/using-modules.md) - How to use modules
|
||||
- [NixOS Modules](./nixos/overview.md) - System-level modules
|
||||
- [Port Management Guide](../../guides/port-management.md) - Detailed port management
|
||||
@@ -0,0 +1,272 @@
|
||||
# ports Home Manager Module
|
||||
|
||||
Port management module for Home Manager.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides centralized port management for user-level services, similar to the NixOS version but with additional support for generating environment variables.
|
||||
|
||||
See [Port Management Guide](../../guides/port-management.md) for detailed usage.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
|
||||
# Define default ports
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
vite = 5173;
|
||||
};
|
||||
|
||||
# Host-specific overrides
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
vite = 5174;
|
||||
};
|
||||
};
|
||||
|
||||
# Current host
|
||||
currentHost = "desktop";
|
||||
|
||||
# Generate environment variables (Home Manager only)
|
||||
generateEnvVars = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `m3ta.ports.enable`
|
||||
|
||||
Enable port management.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.ports.definitions`
|
||||
|
||||
Default port definitions.
|
||||
|
||||
- Type: `attrsOf int`
|
||||
- Default: `{}`
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
vite = 5173;
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.hostOverrides`
|
||||
|
||||
Host-specific port overrides.
|
||||
|
||||
- Type: `attrsOf (attrsOf int)`
|
||||
- Default: `{}`
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
vite = 5174;
|
||||
};
|
||||
desktop = {
|
||||
vite = 5173;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.currentHost`
|
||||
|
||||
Current hostname.
|
||||
|
||||
- Type: `string`
|
||||
- Example: `"desktop"`
|
||||
|
||||
### `m3ta.ports.generateEnvVars`
|
||||
|
||||
Generate environment variables from ports.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
- Home Manager only
|
||||
|
||||
When enabled, generates environment variables like:
|
||||
|
||||
```bash
|
||||
PORT_DEV_SERVER=3000
|
||||
PORT_NEXTJS=3001
|
||||
PORT_VITE=5173
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### `config.m3ta.ports.get "service"`
|
||||
|
||||
Get port for a service with host-specific override.
|
||||
|
||||
```nix
|
||||
home.sessionVariables = {
|
||||
DEV_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
};
|
||||
```
|
||||
|
||||
### `config.m3ta.ports.getHostPorts "hostname"`
|
||||
|
||||
Get all ports for a specific host.
|
||||
|
||||
```nix
|
||||
laptopPorts = config.m3ta.ports.getHostPorts "laptop";
|
||||
# Returns: { dev-server = 3000; vite = 5174; ... }
|
||||
```
|
||||
|
||||
### `config.m3ta.ports.listServices`
|
||||
|
||||
List all defined service names.
|
||||
|
||||
```nix
|
||||
allServices = config.m3ta.ports.listServices;
|
||||
# Returns: ["dev-server" "nextjs" "vite"]
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
When `generateEnvVars = true`, the following environment variables are generated:
|
||||
|
||||
```
|
||||
PORT_<SERVICE_UPPERCASE>=<port_number>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
};
|
||||
generateEnvVars = true;
|
||||
};
|
||||
```
|
||||
|
||||
Generates:
|
||||
|
||||
```bash
|
||||
PORT_DEV_SERVER=3000
|
||||
PORT_NEXTJS=3001
|
||||
```
|
||||
|
||||
You can then use these in scripts:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Use environment variable directly
|
||||
npm start --port=$PORT_DEV_SERVER
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
currentHost = "desktop";
|
||||
};
|
||||
|
||||
home.sessionVariables = {
|
||||
DEV_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Environment Variables
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
nextjs = 3001;
|
||||
vite = 5173;
|
||||
};
|
||||
currentHost = "desktop";
|
||||
generateEnvVars = true;
|
||||
};
|
||||
|
||||
# Now available as environment variables
|
||||
# PORT_DEV_SERVER=3000
|
||||
# PORT_NEXTJS=3001
|
||||
# PORT_VITE=5173
|
||||
}
|
||||
```
|
||||
|
||||
### With Multi-Host Setup
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
vite = 5173;
|
||||
};
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
vite = 5174;
|
||||
};
|
||||
desktop = {
|
||||
vite = 5173;
|
||||
};
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
generateEnvVars = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Shell Scripts
|
||||
|
||||
Create `~/.config/zellij/scripts/dev.ksh`:
|
||||
|
||||
```ksh
|
||||
#!/usr/bin/env ksh
|
||||
# Start dev server using environment variable
|
||||
cd ~/projects/my-app
|
||||
npm start --port=$PORT_DEV_SERVER
|
||||
```
|
||||
|
||||
## Difference from NixOS Module
|
||||
|
||||
The Home Manager version has one additional feature:
|
||||
|
||||
### `generateEnvVars`
|
||||
|
||||
Not available in NixOS module. Generates environment variables for all defined ports:
|
||||
|
||||
```nix
|
||||
# Home Manager
|
||||
m3ta.ports.generateEnvVars = true; # Available
|
||||
|
||||
# NixOS
|
||||
# Not available
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Port Management Guide](../../guides/port-management.md) - Detailed guide
|
||||
- [NixOS Ports Module](../nixos/ports.md) - System-level port management
|
||||
@@ -0,0 +1,508 @@
|
||||
# mem0 NixOS Module
|
||||
|
||||
Mem0 REST API server module for AI memory management.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides a systemd service for the Mem0 REST API server, enabling AI agents to maintain persistent memory across conversations.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.nixosModules.mem0];
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
model = "gpt-4o-mini";
|
||||
};
|
||||
vectorStore = {
|
||||
provider = "qdrant";
|
||||
config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `m3ta.mem0.enable`
|
||||
|
||||
Enable the Mem0 REST API server.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.mem0.package`
|
||||
|
||||
The mem0 package to use.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.mem0`
|
||||
|
||||
### `m3ta.mem0.host`
|
||||
|
||||
Host address to bind the server to.
|
||||
|
||||
- Type: `string`
|
||||
- Default: `"127.0.0.1"`
|
||||
|
||||
### `m3ta.mem0.port`
|
||||
|
||||
Port to run the REST API server on.
|
||||
|
||||
- Type: `port`
|
||||
- Default: `8000`
|
||||
|
||||
### `m3ta.mem0.workers`
|
||||
|
||||
Number of worker processes.
|
||||
|
||||
- Type: `integer`
|
||||
- Default: `1`
|
||||
|
||||
### `m3ta.mem0.logLevel`
|
||||
|
||||
Logging level for the server.
|
||||
|
||||
- Type: `enum` ["critical" "error" "warning" "info" "debug" "trace"]
|
||||
- Default: `"info"`
|
||||
|
||||
### `m3ta.mem0.stateDir`
|
||||
|
||||
Directory to store mem0 data and state.
|
||||
|
||||
- Type: `path`
|
||||
- Default: `"/var/lib/mem0"`
|
||||
|
||||
### `m3ta.mem0.user`
|
||||
|
||||
User account under which mem0 runs.
|
||||
|
||||
- Type: `string`
|
||||
- Default: `"mem0"`
|
||||
|
||||
### `m3ta.mem0.group`
|
||||
|
||||
Group under which mem0 runs.
|
||||
|
||||
- Type: `string`
|
||||
- Default: `"mem0"`
|
||||
|
||||
### `m3ta.mem0.environmentFile`
|
||||
|
||||
Environment file containing additional configuration.
|
||||
|
||||
- Type: `nullOr path`
|
||||
- Default: `null`
|
||||
|
||||
## LLM Configuration
|
||||
|
||||
### `m3ta.mem0.llm.provider`
|
||||
|
||||
LLM provider to use.
|
||||
|
||||
- Type: `enum` ["openai" "anthropic" "azure" "groq" "together" "ollama" "litellm"]
|
||||
- Default: `"openai"`
|
||||
|
||||
### `m3ta.mem0.llm.model`
|
||||
|
||||
Model name to use.
|
||||
|
||||
- Type: `string`
|
||||
- Default: `"gpt-4o-mini"`
|
||||
|
||||
### `m3ta.mem0.llm.apiKeyFile`
|
||||
|
||||
Path to file containing the API key.
|
||||
|
||||
- Type: `nullOr path`
|
||||
- Default: `null`
|
||||
- Example: `"/run/secrets/openai-api-key"`
|
||||
|
||||
### `m3ta.mem0.llm.temperature`
|
||||
|
||||
Temperature parameter for LLM generation.
|
||||
|
||||
- Type: `nullOr float`
|
||||
- Default: `null`
|
||||
|
||||
### `m3ta.mem0.llm.maxTokens`
|
||||
|
||||
Maximum tokens for LLM generation.
|
||||
|
||||
- Type: `nullOr int`
|
||||
- Default: `null`
|
||||
|
||||
### `m3ta.mem0.llm.extraConfig`
|
||||
|
||||
Additional LLM configuration options.
|
||||
|
||||
- Type: `attrs`
|
||||
- Default: `{}`
|
||||
|
||||
## Vector Store Configuration
|
||||
|
||||
### `m3ta.mem0.vectorStore.provider`
|
||||
|
||||
Vector database provider.
|
||||
|
||||
- Type: `enum` ["qdrant" "chroma" "pinecone" "weaviate" "faiss" "pgvector" "redis" "elasticsearch" "milvus"]
|
||||
- Default: `"qdrant"`
|
||||
|
||||
### `m3ta.mem0.vectorStore.config`
|
||||
|
||||
Configuration for the vector store.
|
||||
|
||||
- Type: `attrs`
|
||||
- Default: `{}`
|
||||
|
||||
Example for Qdrant:
|
||||
|
||||
```nix
|
||||
vectorStore.config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
collection_name = "mem0_memories";
|
||||
};
|
||||
```
|
||||
|
||||
Example for pgvector:
|
||||
|
||||
```nix
|
||||
vectorStore.config = {
|
||||
host = "localhost";
|
||||
port = 5432;
|
||||
dbname = "postgres";
|
||||
user = "postgres";
|
||||
password = "postgres";
|
||||
};
|
||||
```
|
||||
|
||||
## Embedder Configuration
|
||||
|
||||
### `m3ta.mem0.embedder.provider`
|
||||
|
||||
Embedding model provider.
|
||||
|
||||
- Type: `nullOr (enum` ["openai" "huggingface" "ollama" "vertexai"])
|
||||
- Default: `null`
|
||||
|
||||
### `m3ta.mem0.embedder.model`
|
||||
|
||||
Embedding model name.
|
||||
|
||||
- Type: `nullOr string`
|
||||
- Default: `null`
|
||||
|
||||
### `m3ta.mem0.embedder.config`
|
||||
|
||||
Configuration for the embedder.
|
||||
|
||||
- Type: `attrs`
|
||||
- Default: `{}`
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Minimal Configuration
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With OpenAI
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
model = "gpt-4";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Local LLM (Ollama)
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
llm = {
|
||||
provider = "ollama";
|
||||
model = "llama2";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Port Management
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
mem0 = 8000;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "mem0";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Qdrant
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
vectorStore = {
|
||||
provider = "qdrant";
|
||||
config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
services.qdrant = {
|
||||
enable = true;
|
||||
port = 6333;
|
||||
};
|
||||
```
|
||||
|
||||
### With Secrets (agenix)
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
age.secrets.openai-api-key = {
|
||||
file = ./secrets/openai-api-key.age;
|
||||
};
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
llm = {
|
||||
apiKeyFile = config.age.secrets.openai-api-key.path;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Service Management
|
||||
|
||||
### Start/Stop/Restart
|
||||
|
||||
```bash
|
||||
# Start service
|
||||
sudo systemctl start mem0
|
||||
|
||||
# Stop service
|
||||
sudo systemctl stop mem0
|
||||
|
||||
# Restart service
|
||||
sudo systemctl restart mem0
|
||||
|
||||
# Check status
|
||||
sudo systemctl status mem0
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
sudo journalctl -u mem0 -f
|
||||
|
||||
# View last 100 lines
|
||||
sudo journalctl -u mem0 -n 100
|
||||
```
|
||||
|
||||
### Service File
|
||||
|
||||
The module creates a systemd service at `/etc/systemd/system/mem0.service` with:
|
||||
|
||||
- Security hardening enabled
|
||||
- Automatic restart on failure
|
||||
- Proper user/group setup
|
||||
|
||||
## API Usage
|
||||
|
||||
### Add Memory
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/v1/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "User prefers coffee over tea",
|
||||
"metadata": {"user_id": "123"}
|
||||
}'
|
||||
```
|
||||
|
||||
### Search Memories
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/v1/memories/search?q=coffee
|
||||
```
|
||||
|
||||
### Update Memory
|
||||
|
||||
```bash
|
||||
curl -X PATCH http://localhost:8000/v1/memories/memory_id \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "User prefers coffee over tea, but also likes chai"
|
||||
}'
|
||||
```
|
||||
|
||||
### Delete Memory
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8000/v1/memories/memory_id
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Required Services
|
||||
|
||||
Depending on your configuration, you may need:
|
||||
|
||||
- **qdrant** service (if using qdrant vector store)
|
||||
- **postgresql** with pgvector (if using pgvector)
|
||||
- **chroma** service (if using chroma)
|
||||
- **ollama** (if using local LLMs)
|
||||
|
||||
### Example: Qdrant
|
||||
|
||||
```nix
|
||||
services.qdrant = {
|
||||
enable = true;
|
||||
port = 6333;
|
||||
};
|
||||
```
|
||||
|
||||
### Example: PostgreSQL
|
||||
|
||||
```nix
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
enableTCPIP = true;
|
||||
package = pkgs.postgresql_15;
|
||||
|
||||
extensions = ["pgvector"];
|
||||
|
||||
settings = {
|
||||
port = 5432;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Firewall
|
||||
|
||||
The module automatically opens the firewall port if binding to non-localhost addresses:
|
||||
|
||||
```nix
|
||||
# Opens port if host is not "127.0.0.1" or "localhost"
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
host = "0.0.0.0"; # Binds to all interfaces
|
||||
port = 8000;
|
||||
};
|
||||
# Firewall automatically opens port 8000
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### User/Group
|
||||
|
||||
Creates dedicated user and group:
|
||||
|
||||
- User: `mem0`
|
||||
- Group: `mem0`
|
||||
- Home: `/var/lib/mem0`
|
||||
|
||||
### Hardening
|
||||
|
||||
Systemd service includes security hardening:
|
||||
|
||||
- `NoNewPrivileges`
|
||||
- `PrivateTmp`
|
||||
- `ProtectSystem=strict`
|
||||
- `ProtectHome=true`
|
||||
- `RestrictRealtime=true`
|
||||
- `RestrictNamespaces=true`
|
||||
- `LockPersonality=true`
|
||||
|
||||
### Secrets
|
||||
|
||||
Use `apiKeyFile` for API keys instead of plain text:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
llm.apiKeyFile = "/run/secrets/openai-api-key";
|
||||
|
||||
# Bad (insecure)
|
||||
llm.apiKey = "sk-xxx";
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
Check logs:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u mem0 -n 50
|
||||
```
|
||||
|
||||
Common issues:
|
||||
|
||||
1. **API key missing**: Ensure `apiKeyFile` exists and is readable
|
||||
2. **Vector store unavailable**: Ensure qdrant/other store is running
|
||||
3. **Port in use**: Check if port is available
|
||||
|
||||
### API Not Responding
|
||||
|
||||
Check service status:
|
||||
|
||||
```bash
|
||||
sudo systemctl status mem0
|
||||
|
||||
# Check if port is open
|
||||
ss -tuln | grep 8000
|
||||
```
|
||||
|
||||
### Memory Issues
|
||||
|
||||
Increase memory limit in systemd override:
|
||||
|
||||
```bash
|
||||
sudo systemctl edit mem0
|
||||
|
||||
[Service]
|
||||
MemoryMax=2G
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [mem0 Package](../../packages/mem0.md) - Package documentation
|
||||
- [Port Management Guide](../../guides/port-management.md) - Using with port management
|
||||
- [Using Modules Guide](../../guides/using-modules.md) - Module usage patterns
|
||||
@@ -0,0 +1,166 @@
|
||||
# NixOS Modules Overview
|
||||
|
||||
Overview of available NixOS modules in m3ta-nixpkgs.
|
||||
|
||||
## Available Modules
|
||||
|
||||
- [ports](./ports.md) - Port management across hosts
|
||||
- [mem0](./mem0.md) - Mem0 REST API server
|
||||
|
||||
## Importing Modules
|
||||
|
||||
### Import All Modules
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Import Specific Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.ports
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Module Namespace
|
||||
|
||||
All NixOS modules use the `m3ta.*` namespace:
|
||||
|
||||
```nix
|
||||
# Port management
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {nginx = 80;};
|
||||
};
|
||||
|
||||
# Mem0 service
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Enable Module
|
||||
|
||||
All modules follow the pattern:
|
||||
|
||||
```nix
|
||||
m3ta.moduleName = {
|
||||
enable = true;
|
||||
# ... options
|
||||
};
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Modules typically provide these sections:
|
||||
|
||||
- `enable` - Enable/disable module
|
||||
- `package` - Custom package (optional)
|
||||
- Configuration options specific to module
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### With Port Management
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
mem0 = 8000;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "mem0";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
root /var/www;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Home Manager
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
# NixOS modules
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
|
||||
# Home Manager integration
|
||||
home-manager.users.myusername = {
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
];
|
||||
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
dev-server = 3000;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Locations
|
||||
|
||||
- `modules/nixos/ports.nix` - Port management module
|
||||
- `modules/nixos/mem0.nix` - Mem0 REST API server module
|
||||
|
||||
## Adding New Modules
|
||||
|
||||
1. Create module file: `modules/nixos/my-module.nix`
|
||||
2. Follow standard pattern:
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
3. Import in `modules/nixos/default.nix`
|
||||
|
||||
## Related
|
||||
|
||||
- [Port Management Guide](../guides/port-management.md) - Detailed port management usage
|
||||
- [Using Modules Guide](../guides/using-modules.md) - How to use modules
|
||||
- [Home Manager Modules](./home-manager/overview.md) - User-level modules
|
||||
@@ -0,0 +1,229 @@
|
||||
# ports NixOS Module
|
||||
|
||||
Port management module for NixOS.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides centralized port management across multiple hosts. Define default ports and host-specific overrides to prevent conflicts.
|
||||
|
||||
See [Port Management Guide](../../guides/port-management.md) for detailed usage.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
|
||||
# Define default ports
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
};
|
||||
|
||||
# Host-specific overrides
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
grafana = 3001;
|
||||
};
|
||||
};
|
||||
|
||||
# Current host
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
### `m3ta.ports.enable`
|
||||
|
||||
Enable port management.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
### `m3ta.ports.definitions`
|
||||
|
||||
Default port definitions.
|
||||
|
||||
- Type: `attrsOf int`
|
||||
- Default: `{}`
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.hostOverrides`
|
||||
|
||||
Host-specific port overrides.
|
||||
|
||||
- Type: `attrsOf (attrsOf int)`
|
||||
- Default: `{}`
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
grafana = 3001;
|
||||
};
|
||||
server = {
|
||||
nginx = 80;
|
||||
prometheus = 9091;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `m3ta.ports.currentHost`
|
||||
|
||||
Current hostname. Determines which overrides to apply.
|
||||
|
||||
- Type: `string`
|
||||
- Example: `config.networking.hostName`
|
||||
|
||||
## Functions
|
||||
|
||||
### `config.m3ta.ports.get "service"`
|
||||
|
||||
Get port for a service with host-specific override.
|
||||
|
||||
```nix
|
||||
services.nginx = {
|
||||
port = config.m3ta.ports.get "nginx";
|
||||
};
|
||||
```
|
||||
|
||||
If current host is `laptop` and `hostOverrides.laptop.nginx = 8080`, returns `8080`.
|
||||
If no override, returns default `80`.
|
||||
|
||||
### `config.m3ta.ports.getHostPorts "hostname"`
|
||||
|
||||
Get all ports for a specific host.
|
||||
|
||||
```nix
|
||||
# Get all ports for laptop
|
||||
laptopPorts = config.m3ta.ports.getHostPorts "laptop";
|
||||
# Returns: { nginx = 8080; grafana = 3000; ... }
|
||||
```
|
||||
|
||||
### `config.m3ta.ports.listServices`
|
||||
|
||||
List all defined service names.
|
||||
|
||||
```nix
|
||||
allServices = config.m3ta.ports.listServices;
|
||||
# Returns: ["nginx" "grafana" "prometheus"]
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
currentHost = "server";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Host Setup
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
};
|
||||
hostOverrides = {
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
grafana = 3001;
|
||||
};
|
||||
server = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### With Multiple Services
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {
|
||||
# Monitoring
|
||||
grafana = 3000;
|
||||
prometheus = 9090;
|
||||
loki = 3100;
|
||||
promtail = 9080;
|
||||
|
||||
# Web
|
||||
nginx = 80;
|
||||
|
||||
# Databases
|
||||
postgres = 5432;
|
||||
redis = 6379;
|
||||
qdrant = 6333;
|
||||
};
|
||||
currentHost = config.networking.hostName;
|
||||
};
|
||||
|
||||
# Use ports
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "postgres";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Port Management Guide](../../guides/port-management.md) - Detailed guide
|
||||
- [Home Manager Ports Module](../home-manager/ports.md) - User-level port management
|
||||
@@ -0,0 +1,23 @@
|
||||
# beads (Removed)
|
||||
|
||||
> **Note**: The `beads` package has been removed from this repository.
|
||||
|
||||
## Why was it removed?
|
||||
|
||||
The beads package was removed as it is no longer actively used.
|
||||
|
||||
## What was beads?
|
||||
|
||||
Beads was a lightweight memory system for AI coding agents with graph-based issue tracking. It provided:
|
||||
- Persistent memory across AI sessions
|
||||
- Graph-based issue tracking with dependencies
|
||||
- Discovered work tracking
|
||||
- Git integration
|
||||
|
||||
If you need beads, you can still build it from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/steveyegge/beads
|
||||
cd beads
|
||||
go build ./cmd/bd
|
||||
```
|
||||
@@ -0,0 +1,206 @@
|
||||
# code2prompt
|
||||
|
||||
A CLI tool that converts your codebase into a single LLM prompt with a source tree, prompt templating, and token counting.
|
||||
|
||||
## Description
|
||||
|
||||
code2prompt is a command-line tool designed to help developers prepare their codebases for analysis by Large Language Models (LLMs). It creates a comprehensive prompt that includes:
|
||||
|
||||
- Source code tree structure
|
||||
- Concatenated file contents
|
||||
- Prompt templates
|
||||
- Token counting for context management
|
||||
|
||||
## Features
|
||||
|
||||
- 📁 **Source Tree Generation**: Visual representation of your codebase structure
|
||||
- 📝 **Code Concatenation**: Merges multiple files into a single prompt
|
||||
- 🧩 **Prompt Templates**: Customizable prompt templates
|
||||
- 🔢 **Token Counting**: Accurate token counting for various LLMs
|
||||
- 🎯 **Selective Inclusion**: Choose specific files and directories
|
||||
- 🔒 **Smart Filtering**: Exclude files by pattern (.git, node_modules, etc.)
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.code2prompt
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#code2prompt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Generate prompt for current directory
|
||||
code2prompt
|
||||
|
||||
# Generate for specific directory
|
||||
code2prompt /path/to/project
|
||||
|
||||
# Output to file
|
||||
code2prompt -o prompt.txt
|
||||
|
||||
# Use custom template
|
||||
code2prompt --template my_template.md
|
||||
```
|
||||
|
||||
### Common Options
|
||||
|
||||
```bash
|
||||
# Include specific files
|
||||
code2prompt --include "*.py" "*.js"
|
||||
|
||||
# Exclude specific files
|
||||
code2prompt --exclude "*.test.*" "node_modules/*"
|
||||
|
||||
# Generate source tree only
|
||||
code2prompt --tree-only
|
||||
|
||||
# Add custom context
|
||||
code2prompt --context "This is a Node.js project"
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### Prepare Codebase for GPT-4
|
||||
|
||||
```bash
|
||||
code2prompt \
|
||||
--template gpt4-template.md \
|
||||
--include "*.ts" "*.tsx" \
|
||||
--exclude "node_modules" "*.test.ts" \
|
||||
--context "Review this TypeScript codebase" \
|
||||
-o codebase_prompt.txt
|
||||
|
||||
# Then feed to GPT-4
|
||||
cat codebase_prompt.txt
|
||||
```
|
||||
|
||||
#### Analyze Specific Directory
|
||||
|
||||
```bash
|
||||
# Analyze only src directory
|
||||
code2prompt src/ \
|
||||
--include "*.rs" \
|
||||
--context "Analyze this Rust codebase" \
|
||||
-o src_analysis.txt
|
||||
```
|
||||
|
||||
#### Create Source Tree
|
||||
|
||||
```bash
|
||||
# Generate only source tree
|
||||
code2prompt --tree-only > tree.txt
|
||||
|
||||
cat tree.txt
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Code Review
|
||||
|
||||
Prepare code for AI-assisted code review:
|
||||
|
||||
```bash
|
||||
code2prompt \
|
||||
--template code-review.md \
|
||||
--include "*.py" \
|
||||
--context "Review this Python code for security issues" \
|
||||
-o review_prompt.txt
|
||||
```
|
||||
|
||||
### Documentation Generation
|
||||
|
||||
Generate documentation using LLMs:
|
||||
|
||||
```bash
|
||||
code2prompt \
|
||||
--template docs-template.md \
|
||||
--include "*.js" "*.md" \
|
||||
--context "Generate API documentation" \
|
||||
-o docs_prompt.txt
|
||||
```
|
||||
|
||||
### Code Migration
|
||||
|
||||
Prepare code for migration assistance:
|
||||
|
||||
```bash
|
||||
code2prompt \
|
||||
--template migration-template.md \
|
||||
--include "*.js" \
|
||||
--context "Migrate this JavaScript to TypeScript" \
|
||||
-o migration_prompt.txt
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `CODE2PROMPT_TEMPLATE_DIR`: Directory containing custom templates
|
||||
- `CODE2PROMPT_DEFAULT_TEMPLATE`: Default template to use
|
||||
|
||||
### Template Files
|
||||
|
||||
Custom templates can include:
|
||||
|
||||
```markdown
|
||||
# Codebase Analysis
|
||||
|
||||
## Context
|
||||
{context}
|
||||
|
||||
## Directory Tree
|
||||
{tree}
|
||||
|
||||
## Code
|
||||
{code}
|
||||
|
||||
## Instructions
|
||||
{instructions}
|
||||
```
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 4.0.2
|
||||
- **Language**: Rust
|
||||
- **License**: MIT
|
||||
- **Source**: [GitHub](https://github.com/mufeedvh/code2prompt)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `openssl` - Secure communication
|
||||
- `pkg-config` - Build configuration
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary)
|
||||
- macOS (may work)
|
||||
- Windows (not tested)
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,190 @@
|
||||
# hyprpaper-random
|
||||
|
||||
Minimal random wallpaper setter for Hyprpaper.
|
||||
|
||||
## Description
|
||||
|
||||
hyprpaper-random is a shell script that randomly selects and applies a wallpaper from a configured directory for use with Hyprpaper on Hyprland. It's designed to be minimal and fast.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎲 **Random Selection**: Picks a random wallpaper from directory
|
||||
- 🖼️ **Multi-Monitor Support**: Applies wallpaper to all monitors
|
||||
- 📁 **Flexible Directory**: Configurable via environment variable
|
||||
- 🔍 **Format Support**: jpg, jpeg, png, webp, avif
|
||||
- ⚡ **Fast**: Uses `fd` for quick file searching
|
||||
- 🔄 **Safe**: Null-safe handling and error checking
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
hyprpaper-random
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.hyprpaper-random
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#hyprpaper-random
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Use default directory ($XDG_CONFIG_HOME/hypr/wallpapers or ~/.config/hypr/wallpapers)
|
||||
hyprpaper-random
|
||||
|
||||
# Use custom directory
|
||||
WALLPAPER_DIR=~/Pictures/wallpapers hyprpaper-random
|
||||
|
||||
# Or set directory permanently
|
||||
export WALLPAPER_DIR=~/Pictures/wallpapers
|
||||
hyprpaper-random
|
||||
```
|
||||
|
||||
### With Hyprpaper
|
||||
|
||||
Make sure Hyprpaper is running and loaded:
|
||||
|
||||
```bash
|
||||
# Start Hyprpaper
|
||||
hyprpaper &
|
||||
|
||||
# Set random wallpaper
|
||||
hyprpaper-random
|
||||
```
|
||||
|
||||
### Automate with Keybinding
|
||||
|
||||
Add to Hyprland config:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
bindm = [
|
||||
"SUPER, mouse, movewindow"
|
||||
];
|
||||
|
||||
bind = [
|
||||
# Set random wallpaper on SUPER + W
|
||||
"SUPER, W, exec, ${pkgs.hyprpaper-random}/bin/hyprpaper-random"
|
||||
];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Automate with Cron
|
||||
|
||||
```bash
|
||||
# Change wallpaper every hour
|
||||
0 * * * * hyprpaper-random
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Directory Setup
|
||||
|
||||
Default wallpaper directory:
|
||||
|
||||
```
|
||||
$XDG_CONFIG_HOME/hypr/wallpapers/
|
||||
# or
|
||||
~/.config/hypr/wallpapers/
|
||||
```
|
||||
|
||||
Custom directory:
|
||||
|
||||
```bash
|
||||
# Temporary
|
||||
WALLPAPER_DIR=~/Pictures/my-wallpapers hyprpaper-random
|
||||
|
||||
# Permanent (add to shell config)
|
||||
export WALLPAPER_DIR=~/Pictures/my-wallpapers
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `WALLPAPER_DIR`: Path to wallpaper directory (default: `$XDG_CONFIG_HOME/hypr/wallpapers`)
|
||||
- `XDG_CONFIG_HOME`: Config directory base (default: `~/.config`)
|
||||
|
||||
## Requirements
|
||||
|
||||
- `hyprland`: Hyprland window manager (for `hyprctl`)
|
||||
- `hyprpaper`: Wallpaper utility for Hyprland
|
||||
- `fd`: Fast file search
|
||||
- `coreutils`: For `shuf` command
|
||||
- `gawk`: Text processing
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary, requires Hyprland)
|
||||
- macOS (not supported)
|
||||
- Windows (not supported)
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.1
|
||||
- **Type**: Shell script
|
||||
- **License**: MIT
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Wallpapers Found
|
||||
|
||||
Error: `No wallpapers found in: /path/to/dir`
|
||||
|
||||
**Solution**: Ensure wallpaper directory exists and contains images:
|
||||
|
||||
```bash
|
||||
ls -la $WALLPAPER_DIR # Check directory exists
|
||||
ls -la $WALLPAPER_DIR/*.jpg # Check for images
|
||||
```
|
||||
|
||||
### Hyprctl Not Found
|
||||
|
||||
Error: `hyprctl: command not found`
|
||||
|
||||
**Solution**: Ensure Hyprland is installed:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
hyprland
|
||||
hyprpaper
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Wallpaper Not Changing
|
||||
|
||||
**Solution**: Check if Hyprpaper is running:
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
hyprctl hyprpaper listloaded
|
||||
|
||||
# Check for errors
|
||||
journalctl -u hyprpaper -f
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,115 @@
|
||||
# kestractl
|
||||
|
||||
CLI for the Kestra workflow orchestration platform.
|
||||
|
||||
## Description
|
||||
|
||||
kestractl is the official command-line interface for [Kestra](https://kestra.io), an open-source workflow orchestration platform. It allows you to interact with Kestra instances to manage flows, trigger executions, inspect namespaces, and automate orchestration tasks from the terminal.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔄 **Flow Management**: Deploy, inspect, and delete flows
|
||||
- ▶️ **Execution Control**: Trigger and monitor workflow executions
|
||||
- 📁 **Namespace Operations**: Manage Kestra namespaces and their resources
|
||||
- 📂 **Namespace Files**: Upload and manage files in namespace storage
|
||||
- 🌐 **Multi-Environment**: Switch between dev, staging, and production contexts
|
||||
- ⚡ **Pre-built Binary**: No compilation required — fetched directly from GitHub releases
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
kestractl
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.kestractl
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#kestractl
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Check version
|
||||
kestractl version
|
||||
|
||||
# Show help
|
||||
kestractl --help
|
||||
|
||||
# Connect to a Kestra instance
|
||||
kestractl context set --api-url http://localhost:8080
|
||||
|
||||
# List flows in a namespace
|
||||
kestractl flow list --namespace my.namespace
|
||||
|
||||
# Trigger a flow execution
|
||||
kestractl execution create --namespace my.namespace --flow-id my-flow
|
||||
|
||||
# Monitor executions
|
||||
kestractl execution list --namespace my.namespace
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
kestractl uses a context system to manage connections to Kestra instances:
|
||||
|
||||
```bash
|
||||
# Create a context for a local instance
|
||||
kestractl context set local --api-url http://localhost:8080
|
||||
|
||||
# Create a context for a remote instance with auth
|
||||
kestractl context set prod --api-url https://kestra.example.com --token <your-token>
|
||||
|
||||
# Switch active context
|
||||
kestractl context use prod
|
||||
```
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 1.0.0
|
||||
- **Language**: Go (pre-built binary)
|
||||
- **License**: Apache 2.0
|
||||
- **Source**: [GitHub](https://github.com/kestra-io/kestractl)
|
||||
|
||||
## Platform Support
|
||||
|
||||
- `x86_64-linux`
|
||||
- `aarch64-linux`
|
||||
|
||||
## Package Structure
|
||||
|
||||
This package uses a `sources.json` + `update.sh` pattern for multi-platform binary fetching:
|
||||
|
||||
```
|
||||
pkgs/kestractl/
|
||||
├── default.nix — reads version + hashes from sources.json
|
||||
├── sources.json — per-platform URLs and SRI hashes
|
||||
└── update.sh — fetches latest GitHub release, updates sources.json
|
||||
```
|
||||
|
||||
Updates are handled by `update.sh` (called by the Gitea Actions nix-update workflow), which fetches the latest release from GitHub, downloads each platform's tarball, computes SRI hashes, and rewrites `sources.json`.
|
||||
|
||||
## Related
|
||||
|
||||
- [Kestra Documentation](https://kestra.io/docs)
|
||||
- [kestractl GitHub](https://github.com/kestra-io/kestractl)
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,180 @@
|
||||
# launch-webapp
|
||||
|
||||
Launches a web app using your default browser in app mode.
|
||||
|
||||
## Description
|
||||
|
||||
launch-webapp is a shell script that launches web applications (like Discord, Spotify Web, etc.) in your default browser's "app mode". This provides a more native-like experience for web apps.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌐 **Auto-Detection**: Detects your default web browser
|
||||
- 🚀 **App Mode**: Launches in dedicated app window (no address bar)
|
||||
- 🎨 **Native Feel**: Removes browser chrome for app-like experience
|
||||
- 🔄 **Session Management**: Keeps web apps separate from regular browsing
|
||||
- 🖥️ **Wayland Support**: Works with Wayland session managers (via `uwsm`)
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
launch-webapp
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.launch-webapp
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#launch-webapp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Launch web app
|
||||
launch-webapp https://web.telegram.org
|
||||
|
||||
# Launch with additional arguments
|
||||
launch-webapp https://web.whatsapp.com --app-name="WhatsApp"
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### Launch Discord Web
|
||||
|
||||
```bash
|
||||
launch-webapp https://discord.com/app
|
||||
```
|
||||
|
||||
#### Launch Spotify Web
|
||||
|
||||
```bash
|
||||
launch-webapp https://open.spotify.com
|
||||
```
|
||||
|
||||
#### Launch Google Chat
|
||||
|
||||
```bash
|
||||
launch-webapp https://chat.google.com
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Supported Browsers
|
||||
|
||||
The script auto-detects and supports:
|
||||
|
||||
- Google Chrome
|
||||
- Brave Browser
|
||||
- Microsoft Edge
|
||||
- Opera
|
||||
- Vivaldi
|
||||
- Chromium (fallback)
|
||||
|
||||
### Default Browser
|
||||
|
||||
The script uses `xdg-settings` to detect your default browser.
|
||||
|
||||
```bash
|
||||
# Check your default browser
|
||||
xdg-settings get default-web-browser
|
||||
```
|
||||
|
||||
### Wayland Support
|
||||
|
||||
The script uses `uwsm` (Wayland Session Manager) for proper Wayland support. Ensure `uwsm` is installed and configured.
|
||||
|
||||
## Desktop Integration
|
||||
|
||||
### Create Desktop Entry
|
||||
|
||||
Create `~/.local/share/applications/webapp-discord.desktop`:
|
||||
|
||||
```ini
|
||||
[Desktop Entry]
|
||||
Name=Discord Web
|
||||
Comment=Discord Web App
|
||||
Exec=launch-webapp https://discord.com/app
|
||||
Icon=discord
|
||||
Type=Application
|
||||
Categories=Network;InstantMessaging;
|
||||
```
|
||||
|
||||
### Add to Menu
|
||||
|
||||
The desktop entry will appear in your application menu after creating it.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `xdg-utils`: For default browser detection
|
||||
- `uwsm`: Wayland session manager
|
||||
- Your preferred browser (Chrome, Brave, etc.)
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary, requires Wayland)
|
||||
- macOS (not tested)
|
||||
- Windows (not supported)
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.0
|
||||
- **Type**: Shell script
|
||||
- **License**: MIT
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Browser Not Found
|
||||
|
||||
If the script doesn't find your browser, ensure it's installed:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
google-chrome
|
||||
# or brave-browser, microsoft-edge, etc.
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Wayland Issues
|
||||
|
||||
If you encounter Wayland issues:
|
||||
|
||||
```bash
|
||||
# Check uwsm is installed
|
||||
which uwsm
|
||||
|
||||
# Check Wayland session
|
||||
echo $XDG_SESSION_TYPE # Should be "wayland"
|
||||
```
|
||||
|
||||
### App Won't Launch
|
||||
|
||||
Check the browser supports app mode:
|
||||
|
||||
```bash
|
||||
# Test manually
|
||||
google-chrome --app=https://example.com
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,268 @@
|
||||
# mem0
|
||||
|
||||
Long-term memory layer for AI agents with REST API support.
|
||||
|
||||
## Description
|
||||
|
||||
Mem0 provides a sophisticated memory management system for AI applications and agents. It enables AI assistants to maintain persistent memory across conversations and sessions using vector storage.
|
||||
|
||||
## Features
|
||||
|
||||
- 💾 **Long-term Memory**: Persistent memory across conversations
|
||||
- 🔍 **Semantic Search**: Vector-based similarity search
|
||||
- 🤖 **Multiple LLM Support**: OpenAI, Anthropic, Groq, Ollama, etc.
|
||||
- 📊 **Vector Stores**: Qdrant, Chroma, Pinecone, pgvector, etc.
|
||||
- 🌐 **REST API**: Easy integration via HTTP endpoints
|
||||
- 🎯 **Multi-modal Support**: Text and image memories
|
||||
- 🔧 **Configurable**: Flexible LLM and embedding models
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
mem0
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.mem0
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#mem0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line
|
||||
|
||||
```bash
|
||||
# Start mem0 server
|
||||
mem0-server
|
||||
|
||||
# With custom port
|
||||
MEM0_PORT=8080 mem0-server
|
||||
|
||||
# With LLM provider
|
||||
MEM0_LLM_PROVIDER=openai OPENAI_API_KEY=sk-xxx mem0-server
|
||||
```
|
||||
|
||||
### Python Library
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
# Initialize with OpenAI
|
||||
memory = Memory(
|
||||
llm_provider="openai",
|
||||
llm_model="gpt-4o-mini",
|
||||
vector_store="qdrant",
|
||||
openai_api_key="sk-xxx"
|
||||
)
|
||||
|
||||
# Add a memory
|
||||
result = memory.add(
|
||||
"I prefer coffee over tea",
|
||||
metadata={"user_id": "123"}
|
||||
)
|
||||
|
||||
# Search memories
|
||||
memories = memory.search("What does the user prefer?")
|
||||
```
|
||||
|
||||
### REST API
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
mem0-server
|
||||
|
||||
# Add memory
|
||||
curl -X POST http://localhost:8000/v1/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "User likes Python"}'
|
||||
|
||||
# Search memories
|
||||
curl http://localhost:8000/v1/memories/search?q=Python
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# LLM Configuration
|
||||
export MEM0_LLM_PROVIDER=openai
|
||||
export MEM0_LLM_MODEL=gpt-4o-mini
|
||||
export MEM0_LLM_TEMPERATURE=0.7
|
||||
export OPENAI_API_KEY=sk-xxx
|
||||
|
||||
# Vector Store
|
||||
export MEM0_VECTOR_PROVIDER=qdrant
|
||||
export QDRANT_HOST=localhost
|
||||
export QDRANT_PORT=6333
|
||||
|
||||
# Server
|
||||
export MEM0_HOST=0.0.0.0
|
||||
export MEM0_PORT=8000
|
||||
export MEM0_WORKERS=4
|
||||
export MEM0_LOG_LEVEL=info
|
||||
```
|
||||
|
||||
### NixOS Module (Recommended)
|
||||
|
||||
Use the NixOS module for production:
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.nixosModules.mem0];
|
||||
|
||||
m3ta.mem0 = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
llm = {
|
||||
provider = "openai";
|
||||
apiKeyFile = "/run/secrets/openai-api-key";
|
||||
model = "gpt-4o-mini";
|
||||
};
|
||||
vectorStore = {
|
||||
provider = "qdrant";
|
||||
config = {
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See [mem0 Module](../modules/nixos/mem0.md) for full module documentation.
|
||||
|
||||
## Supported LLM Providers
|
||||
|
||||
| Provider | Model Examples | Notes |
|
||||
|----------|---------------|-------|
|
||||
| `openai` | gpt-4, gpt-3.5-turbo | Most tested |
|
||||
| `anthropic` | claude-3-opus, claude-3-sonnet | Requires key |
|
||||
| `groq` | mixtral-8x7b-32768 | Fast inference |
|
||||
| `ollama` | llama2, mistral | Local only |
|
||||
| `together` | llama-2-70b | API access |
|
||||
|
||||
## Supported Vector Stores
|
||||
|
||||
| Provider | Requirements | Notes |
|
||||
|----------|--------------|-------|
|
||||
| `qdrant` | qdrant server | Recommended |
|
||||
| `chroma` | chroma server | Simple setup |
|
||||
| `pgvector` | PostgreSQL + pgvector | SQL-based |
|
||||
| `pinecone` | Pinecone API | Cloud only |
|
||||
| `redis` | Redis stack | Fast |
|
||||
| `elasticsearch` | ES cluster | Scalable |
|
||||
|
||||
## Use Cases
|
||||
|
||||
### AI Chatbot with Memory
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
memory = Memory()
|
||||
|
||||
# During conversation
|
||||
memory.add("User works at Google as a software engineer")
|
||||
|
||||
# Later conversations
|
||||
memories = memory.search("Where does the user work?")
|
||||
# Returns: ["User works at Google as a software engineer"]
|
||||
```
|
||||
|
||||
### Personal Assistant
|
||||
|
||||
```python
|
||||
memory = Memory()
|
||||
|
||||
# Store preferences
|
||||
memory.add("User prefers dark mode", metadata={"category": "preferences"})
|
||||
memory.add("User is vegetarian", metadata={"category": "diet"})
|
||||
|
||||
# Retrieve relevant info
|
||||
memories = memory.search("What should I cook for the user?")
|
||||
```
|
||||
|
||||
### Code Assistant
|
||||
|
||||
```python
|
||||
memory = Memory()
|
||||
|
||||
# Store project context
|
||||
memory.add("This is a NixOS project with custom packages")
|
||||
|
||||
# Later analysis
|
||||
memories = memory.search("What kind of project is this?")
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Vector Store
|
||||
|
||||
You need a vector store running. Example with Qdrant:
|
||||
|
||||
```nix
|
||||
services.qdrant = {
|
||||
enable = true;
|
||||
port = 6333;
|
||||
};
|
||||
```
|
||||
|
||||
### LLM Provider
|
||||
|
||||
You need API keys or local models:
|
||||
|
||||
```nix
|
||||
# OpenAI
|
||||
services.mem0.llm.apiKeyFile = "/run/secrets/openai-api-key";
|
||||
|
||||
# Or use agenix
|
||||
age.secrets.openai-api-key.file = ./secrets/openai.age;
|
||||
```
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 1.0.0
|
||||
- **Language**: Python
|
||||
- **License**: Apache-2.0
|
||||
- **Source**: [GitHub](https://github.com/mem0ai/mem0)
|
||||
|
||||
## Python Dependencies
|
||||
|
||||
- `litellm` - Multi-LLM support
|
||||
- `qdrant-client` - Qdrant client
|
||||
- `pydantic` - Data validation
|
||||
- `openai` - OpenAI client
|
||||
- `fastapi` - REST API
|
||||
- `uvicorn` - ASGI server
|
||||
- `sqlalchemy` - Database ORM
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary)
|
||||
- macOS (may work)
|
||||
- Windows (not tested)
|
||||
|
||||
## Related
|
||||
|
||||
- [mem0 Module](../modules/nixos/mem0.md) - NixOS module documentation
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Port Management](../guides/port-management.md) - Managing service ports
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,172 @@
|
||||
# msty-studio
|
||||
|
||||
Msty Studio enables advanced, privacy‑preserving AI workflows entirely on your local machine.
|
||||
|
||||
## Description
|
||||
|
||||
Msty Studio is a desktop application that provides a powerful AI development environment with a focus on privacy. All AI processing happens locally on your machine, ensuring your data never leaves your system.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔒 **Privacy-First**: All AI processing happens locally
|
||||
- 🤖 **Local AI Models**: Support for running local LLMs
|
||||
- 📝 **Code Editor**: Integrated development environment
|
||||
- 🔄 **Multiple Models**: Switch between different AI models
|
||||
- 📊 **Model Management**: Download and manage local models
|
||||
- 🎨 **Modern UI**: Clean, intuitive interface
|
||||
- ⚡ **Fast Performance**: Optimized for speed
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
msty-studio
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.msty-studio
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#msty-studio
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Launch Application
|
||||
|
||||
```bash
|
||||
# Launch Msty Studio
|
||||
msty-studio
|
||||
|
||||
# Or from application menu
|
||||
# Applications -> Msty Studio
|
||||
```
|
||||
|
||||
### First Run
|
||||
|
||||
On first launch, you'll need to:
|
||||
|
||||
1. Download an AI model (if not already downloaded)
|
||||
2. Configure model settings
|
||||
3. Set up your workspace
|
||||
|
||||
### Managing Models
|
||||
|
||||
Through the Msty Studio interface, you can:
|
||||
|
||||
- Download new models
|
||||
- Remove unused models
|
||||
- Switch between models
|
||||
- Configure model parameters
|
||||
|
||||
## Configuration
|
||||
|
||||
### Model Directory
|
||||
|
||||
Models are stored in your home directory:
|
||||
|
||||
```
|
||||
~/.local/share/msty-studio/models/
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
||||
Msty Studio stores settings in:
|
||||
|
||||
```
|
||||
~/.config/msty-studio/
|
||||
```
|
||||
|
||||
### Integration with Nix
|
||||
|
||||
The package includes required dependencies:
|
||||
|
||||
- Node.js (for runtime)
|
||||
- npm (for package management)
|
||||
- uv (for Python packages)
|
||||
- Python (for AI models)
|
||||
|
||||
## Requirements
|
||||
|
||||
### System Requirements
|
||||
|
||||
- Linux (x86_64)
|
||||
- 8GB RAM minimum (16GB+ recommended)
|
||||
- Modern CPU with AVX2 support
|
||||
- GPU recommended (for faster inference)
|
||||
|
||||
### Dependencies
|
||||
|
||||
The package includes these dependencies:
|
||||
|
||||
- `nodejs` - JavaScript runtime
|
||||
- `nodePackages.npm` - Package manager
|
||||
- `uv` - Python package installer
|
||||
- `python3` - Python runtime
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (x86_64 only)
|
||||
- macOS (not supported)
|
||||
- Windows (not supported)
|
||||
|
||||
**Note**: Msty Studio is distributed as an AppImage for Linux.
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 2.0.0-beta.4
|
||||
- **Type**: AppImage
|
||||
- **License**: Proprietary (Unfree)
|
||||
- **Source**: [msty.studio](https://msty.studio)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### AppImage Won't Launch
|
||||
|
||||
Ensure executable bit is set:
|
||||
|
||||
```bash
|
||||
chmod +x ~/.local/share/applications/msty-studio.desktop
|
||||
```
|
||||
|
||||
### Models Not Downloading
|
||||
|
||||
Check disk space and network:
|
||||
|
||||
```bash
|
||||
# Check disk space
|
||||
df -h
|
||||
|
||||
# Check network
|
||||
ping google.com
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
- Ensure you're using a GPU-accelerated model
|
||||
- Close other resource-intensive applications
|
||||
- Check system resources:
|
||||
|
||||
```bash
|
||||
htop
|
||||
nvidia-smi # If using NVIDIA GPU
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,310 @@
|
||||
# n8n
|
||||
|
||||
Free and source-available fair-code licensed workflow automation tool. Easily automate tasks across different services.
|
||||
|
||||
## Description
|
||||
|
||||
n8n (pronounced "n-eight-n") is a workflow automation tool that helps you connect different services and automate tasks without writing code. It features a visual node-based editor for creating workflows with hundreds of integrations.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎨 **Visual Workflow Editor**: Drag-and-drop interface for creating workflows
|
||||
- 🔗 **Hundreds of Integrations**: Connect to popular services (Slack, GitHub, Google, etc.)
|
||||
- 🔄 **Webhook Support**: Trigger workflows via HTTP requests
|
||||
- 📝 **Code Node**: Execute JavaScript/TypeScript code within workflows
|
||||
- 🚀 **Cloud & Self-Hosted**: Use n8n Cloud or self-host on your own infrastructure
|
||||
- 📊 **Data Transformation**: Map and transform data between services
|
||||
- ⏰ **Scheduling**: Run workflows on schedules (cron-like)
|
||||
- 🔒 **Security**: Credential management and secure data handling
|
||||
- 🎯 **Fair-Code License**: Source available with usage restrictions for commercial use
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
n8n
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.n8n
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#n8n
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Start n8n (default configuration)
|
||||
n8n start
|
||||
|
||||
# Start with custom configuration file
|
||||
n8n start --config /path/to/config
|
||||
|
||||
# Run in worker mode (for production with queue)
|
||||
n8n worker
|
||||
|
||||
# Execute a workflow from file
|
||||
n8n execute /path/to/workflow.json
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
|
||||
```bash
|
||||
# Start with tunnel (external access)
|
||||
n8n start --tunnel
|
||||
|
||||
# Disable telemetry
|
||||
n8n start --no-telemetry
|
||||
|
||||
# Specify workflow directory
|
||||
n8n start --workflows /path/to/workflows
|
||||
|
||||
# Enable/disable features
|
||||
n8n start --enable-editor
|
||||
n8n start --disable-metrics
|
||||
```
|
||||
|
||||
### Workflow Management
|
||||
|
||||
```bash
|
||||
# Export workflow to file
|
||||
n8n export:workflow --id=123 --output=workflow.json
|
||||
|
||||
# Import workflow from file
|
||||
n8n import:workflow --input=workflow.json
|
||||
|
||||
# Execute workflow
|
||||
n8n execute:workflow --id=123
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `N8N_BASIC_AUTH_ACTIVE`: Enable basic authentication (default: false)
|
||||
- `N8N_BASIC_AUTH_USER`: Basic auth username
|
||||
- `N8N_BASIC_AUTH_PASSWORD`: Basic auth password
|
||||
- `N8N_ENCRYPTION_KEY`: Encryption key for credentials
|
||||
- `N8N_HOST`: Host URL for web UI (default: localhost)
|
||||
- `N8N_PORT`: Port for web UI (default: 5678)
|
||||
- `N8N_PROTOCOL`: Protocol (http or https)
|
||||
- `N8N_PATH`: Path to mount n8n (default: /)
|
||||
- `N8N_EDITOR_BASE_URL`: Base URL for editor
|
||||
- `N8N_WEBHOOK_URL`: URL for webhook endpoints
|
||||
- `N8N_TIMEZONE`: Timezone for execution (default: UTC)
|
||||
- `N8N_LOG_LEVEL`: Logging level (output, warn, error, verbose)
|
||||
- `N8N_LOG_OUTPUT`: Log output destination (console, file)
|
||||
- `N8N_METRICS`: Enable metrics collection (default: true)
|
||||
- `DB_TYPE`: Database type (sqlite3db, postgresdb, mysqldb)
|
||||
- `DB_SQLITE_VACUUM_ON_STARTUP`: Vacuum SQLite on startup
|
||||
|
||||
### Database Configuration
|
||||
|
||||
By default, n8n uses SQLite for simplicity. For production, use PostgreSQL or MySQL:
|
||||
|
||||
```bash
|
||||
# PostgreSQL
|
||||
export DB_TYPE=postgresdb
|
||||
export DB_POSTGRESDB_HOST=localhost
|
||||
export DB_POSTGRESDB_PORT=5432
|
||||
export DB_POSTGRESDB_DATABASE=n8n
|
||||
export DB_POSTGRESDB_USER=n8n
|
||||
export DB_POSTGRESDB_PASSWORD=yourpassword
|
||||
|
||||
# MySQL/MariaDB
|
||||
export DB_TYPE=mysqldb
|
||||
export DB_MYSQLDB_HOST=localhost
|
||||
export DB_MYSQLDB_PORT=3306
|
||||
export DB_MYSQLDB_DATABASE=n8n
|
||||
export DB_MYSQLDB_USER=n8n
|
||||
export DB_MYSQLDB_PASSWORD=yourpassword
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
Set up encryption for credentials:
|
||||
|
||||
```bash
|
||||
# Generate encryption key
|
||||
export N8N_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
||||
|
||||
# Use in production
|
||||
export N8N_ENCRYPTION_KEY="your-32-char-encryption-key"
|
||||
```
|
||||
|
||||
## NixOS Module
|
||||
|
||||
For NixOS, use the n8n module from nixpkgs for a complete service configuration:
|
||||
|
||||
```nix
|
||||
services.n8n = {
|
||||
enable = true;
|
||||
settings = {
|
||||
host = "0.0.0.0";
|
||||
port = 5678;
|
||||
timezone = "UTC";
|
||||
};
|
||||
environment = {
|
||||
N8N_ENCRYPTION_KEY = "your-encryption-key";
|
||||
DB_TYPE = "postgresdb";
|
||||
DB_POSTGRESDB_HOST = "/var/run/postgresql";
|
||||
DB_POSTGRESDB_DATABASE = "n8n";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Webhook Automation
|
||||
|
||||
Trigger workflows via HTTP requests:
|
||||
|
||||
```bash
|
||||
# Start n8n with tunnel for public URL
|
||||
n8n start --tunnel
|
||||
|
||||
# Create webhook workflow in UI
|
||||
# Workflow receives data from external service
|
||||
# Process and send to another service
|
||||
```
|
||||
|
||||
### Scheduled Tasks
|
||||
|
||||
Run tasks on a schedule:
|
||||
|
||||
```javascript
|
||||
// In workflow editor, use Schedule Trigger
|
||||
// Set cron expression: "0 9 * * *" (daily at 9 AM)
|
||||
// Connect to nodes that perform tasks
|
||||
```
|
||||
|
||||
### Data Sync Between Services
|
||||
|
||||
Keep data synchronized:
|
||||
|
||||
```bash
|
||||
# Create workflow with:
|
||||
# 1. Webhook trigger (service A)
|
||||
# 2. Data transformation node
|
||||
# 3. HTTP request node (service B)
|
||||
# 4. Response back to service A
|
||||
```
|
||||
|
||||
### Automated Reporting
|
||||
|
||||
Generate and send reports:
|
||||
|
||||
```javascript
|
||||
// Workflow steps:
|
||||
// 1. Schedule trigger (daily/weekly)
|
||||
// 2. Database query node
|
||||
// 3. Data formatting
|
||||
// 4. Email or Slack notification
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Slack Integration
|
||||
|
||||
```javascript
|
||||
// Create Slack notification workflow
|
||||
// 1. Trigger (webhook or schedule)
|
||||
// 2. Process data
|
||||
// 3. Send message to Slack channel
|
||||
```
|
||||
|
||||
### GitHub Integration
|
||||
|
||||
```javascript
|
||||
// GitHub repository automation
|
||||
// 1. GitHub webhook trigger (push, PR, issue)
|
||||
// 2. Conditional logic
|
||||
// 3. Actions (create issue, comment, etc.)
|
||||
```
|
||||
|
||||
### Email Automation
|
||||
|
||||
```javascript
|
||||
// Email processing workflow
|
||||
// 1. Email trigger (IMAP)
|
||||
// 2. Parse email content
|
||||
// 3. Process data
|
||||
// 4. Send response or forward
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Production Tips
|
||||
|
||||
- Use PostgreSQL or MySQL instead of SQLite
|
||||
- Enable queue mode with Redis
|
||||
- Use worker nodes for scaling
|
||||
- Configure proper resource limits
|
||||
- Set up load balancing for web UI
|
||||
|
||||
### Queue Mode
|
||||
|
||||
```bash
|
||||
# Start main n8n process
|
||||
export QUEUE_BULL_REDIS_HOST=redis-server
|
||||
export QUEUE_BULL_REDIS_PORT=6379
|
||||
n8n start
|
||||
|
||||
# Start worker processes
|
||||
n8n worker
|
||||
```
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 2.4.1
|
||||
- **Language**: TypeScript/JavaScript (Node.js)
|
||||
- **Package Manager**: pnpm
|
||||
- **License**: Sustainable Use (Fair-Code)
|
||||
- **Source**: [GitHub](https://github.com/n8n-io/n8n)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `nodejs` - JavaScript runtime
|
||||
- `pnpm` - Package manager (build-time)
|
||||
- `python3` - Required for SQLite bindings
|
||||
- `node-gyp` - Node.js native addon build tool
|
||||
- `libkrb5` - Kerberos authentication
|
||||
- `libmongocrypt` - MongoDB encryption
|
||||
- `libpq` - PostgreSQL client library
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux
|
||||
- macOS
|
||||
|
||||
## Notes
|
||||
|
||||
- Package has ~80,000 files, so stripping and ELF patching are disabled for performance
|
||||
- SQLite3 bindings are rebuilt during build phase
|
||||
- TypeScript files and source maps are removed in preInstall phase
|
||||
- Non-deterministic files (.turbo, .modules.yaml, types) are removed
|
||||
- Node modules are pruned to production dependencies only
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
- [n8n Documentation](https://docs.n8n.io) - Official documentation
|
||||
- [n8n Community](https://community.n8n.io) - Community forum
|
||||
@@ -0,0 +1,117 @@
|
||||
# notesmd-cli
|
||||
|
||||
Obsidian CLI (Community) - Interact with Obsidian in the terminal.
|
||||
|
||||
## Description
|
||||
|
||||
notesmd-cli is a command-line interface for interacting with Obsidian, the popular knowledge management and note-taking application. It allows you to create, search, and manipulate notes directly from the terminal.
|
||||
|
||||
## Features
|
||||
|
||||
- 📝 **Note Creation**: Create new notes from the command line
|
||||
- 🔍 **Search**: Search through your Obsidian vault
|
||||
- 📂 **Vault Management**: Interact with your vault structure
|
||||
- 🔗 **WikiLink Support**: Work with Obsidian's WikiLink format
|
||||
- 🏷️ **Tag Support**: Manage and search by tags
|
||||
- ⚡ **Fast**: Lightweight Go binary with no external dependencies
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
notesmd-cli
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.notesmd-cli
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#notesmd-cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Show help
|
||||
notesmd-cli --help
|
||||
|
||||
# Create a new note
|
||||
notesmd-cli new "My Note Title"
|
||||
|
||||
# Search notes
|
||||
notesmd-cli search "search term"
|
||||
|
||||
# List notes
|
||||
notesmd-cli list
|
||||
```
|
||||
|
||||
### Working with Vaults
|
||||
|
||||
```bash
|
||||
# Specify vault path
|
||||
notesmd-cli --vault /path/to/vault new "Note Title"
|
||||
|
||||
# Open a note in Obsidian
|
||||
notesmd-cli open "Note Name"
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```bash
|
||||
# Search with tags
|
||||
notesmd-cli search --tag "project"
|
||||
|
||||
# Append to existing note
|
||||
notesmd-cli append "Note Name" "Additional content"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `OBSIDIAN_VAULT`: Default vault path
|
||||
|
||||
### Command Line Options
|
||||
|
||||
Run `notesmd-cli --help` for a complete list of options.
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.3.0
|
||||
- **Language**: Go
|
||||
- **License**: MIT
|
||||
- **Source**: [GitHub](https://github.com/Yakitrak/notesmd-cli)
|
||||
- **Vendor Hash**: null (no external dependencies)
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux
|
||||
- macOS (Unix systems)
|
||||
|
||||
## Notes
|
||||
|
||||
- No vendor dependencies (pure Go stdlib)
|
||||
- The binary is named `notesmd-cli` (not `notesmd`)
|
||||
- This is the community CLI, not the official Obsidian CLI
|
||||
|
||||
## Related
|
||||
|
||||
- [Obsidian](https://obsidian.md) - The Obsidian application
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,37 @@
|
||||
# opencode (Deprecated)
|
||||
|
||||
> **Note**: The `opencode` package has been removed from this repository.
|
||||
|
||||
## Why was it removed?
|
||||
|
||||
OpenCode (CLI version) has been removed because there is now a well-maintained upstream repository for AI coding tools:
|
||||
|
||||
**[numtide/llm-agents.nix](https://github.com/numtide/llm-agents.nix)**
|
||||
|
||||
This repository provides Nix packages for various AI coding agents, including OpenCode and others, with active maintenance and updates.
|
||||
|
||||
## What should I use instead?
|
||||
|
||||
Use the [llm-agents.nix](https://github.com/numtide/llm-agents.nix) flake directly:
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
llm-agents.url = "github:numtide/llm-agents.nix";
|
||||
};
|
||||
|
||||
outputs = { inputs, ... }: {
|
||||
# Access packages via inputs.llm-agents.packages.${system}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Or run directly:
|
||||
|
||||
```bash
|
||||
nix run github:numtide/llm-agents.nix#opencode
|
||||
```
|
||||
|
||||
## What about opencode-desktop?
|
||||
|
||||
The `opencode-desktop` package remains available in this repository as it includes a Wayland support workaround for [upstream issue #11755](https://github.com/opencode-ai/opencode/issues/11755). Once this issue is resolved upstream, `opencode-desktop` may also be removed in favor of the llm-agents.nix repository.
|
||||
@@ -0,0 +1,220 @@
|
||||
# pomodoro-timer
|
||||
|
||||
A work timer based on the Pomodoro Technique.
|
||||
|
||||
## Description
|
||||
|
||||
A simple, shell-based Pomodoro timer that uses `timer`, Kitty terminal, Rofi, libnotify, and speech synthesis to provide visual and audio feedback for work and break sessions.
|
||||
|
||||
## Features
|
||||
|
||||
- ⏱️ **Pomodoro Technique**: 45-minute work, 10-minute break cycles
|
||||
- 🎨 **Terminal UI**: Floating Kitty terminal window
|
||||
- 📋 **Rofi Menu**: Easy session selection
|
||||
- 🔔 **Notifications**: Desktop and voice notifications
|
||||
- ⚙️ **Custom Times**: Set custom durations
|
||||
- 🎯 **Quick Access**: Simple keybinding integration
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
launch-timer # The main program is named launch-timer
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.launch-timer
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#launch-timer
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Launch timer selection menu
|
||||
launch-timer
|
||||
```
|
||||
|
||||
This opens a Rofi menu with three options:
|
||||
|
||||
1. **work** - 45-minute work session
|
||||
2. **break** - 10-minute break session
|
||||
3. **custom** - Custom time duration
|
||||
|
||||
### Session Options
|
||||
|
||||
#### Work Session
|
||||
|
||||
```bash
|
||||
# Select "work" from menu
|
||||
# Starts 45-minute timer
|
||||
```
|
||||
|
||||
#### Break Session
|
||||
|
||||
```bash
|
||||
# Select "break" from menu
|
||||
# Starts 10-minute timer
|
||||
```
|
||||
|
||||
#### Custom Time
|
||||
|
||||
```bash
|
||||
# Select "custom" from menu
|
||||
# Enter time in format: 25m, 1h, 30s
|
||||
|
||||
# Examples:
|
||||
# 25m - 25 minutes
|
||||
# 1h - 1 hour
|
||||
# 30s - 30 seconds
|
||||
```
|
||||
|
||||
### Time Formats
|
||||
|
||||
Supported formats:
|
||||
|
||||
| Format | Example | Description |
|
||||
|---------|----------|-------------|
|
||||
| `Xm` | `25m` | X minutes |
|
||||
| `Xh` | `1h` | X hours |
|
||||
| `Xs` | `30s` | X seconds |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Keybinding Integration
|
||||
|
||||
Add to Hyprland config:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
bind = [
|
||||
# Launch Pomodoro timer with SUPER + T
|
||||
"SUPER, T, exec, ${pkgs.launch-timer}/bin/launch-timer"
|
||||
];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Defaults
|
||||
|
||||
Modify the script for custom defaults:
|
||||
|
||||
```bash
|
||||
# Change work duration
|
||||
start_timer "60m" "work"
|
||||
|
||||
# Change break duration
|
||||
start_timer "15m" "break"
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `timer` - Terminal timer utility
|
||||
- `kitty` - Terminal emulator
|
||||
- `rofi` - Application launcher
|
||||
- `libnotify` - Desktop notifications
|
||||
- `speechd` - Text-to-speech synthesis
|
||||
|
||||
### System Requirements
|
||||
|
||||
- Linux (primary)
|
||||
- Desktop environment with notification support
|
||||
- Audio output for speech synthesis
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary)
|
||||
- macOS (not supported)
|
||||
- Windows (not supported)
|
||||
|
||||
## Behavior
|
||||
|
||||
### Timer Window
|
||||
|
||||
- Opens as floating Kitty window
|
||||
- Window class: `floating-pomodoro`
|
||||
- Window title: `floating-pomodoro`
|
||||
|
||||
### Notifications
|
||||
|
||||
When session ends, you'll receive:
|
||||
|
||||
1. Desktop notification: "work session ended!" or "break session ended!"
|
||||
2. Voice announcement: "work session ended" or "break session ended"
|
||||
|
||||
### Input Validation
|
||||
|
||||
For custom times:
|
||||
|
||||
- Valid: `25m`, `1h`, `30s`, `1h30m`
|
||||
- Invalid: `25`, `abc`, `1.5h`
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.0
|
||||
- **Type**: Shell script
|
||||
- **License**: MIT
|
||||
- **Main Program**: `launch-timer`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Kitty Not Found
|
||||
|
||||
Ensure Kitty is installed:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
kitty
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### No Notifications
|
||||
|
||||
Ensure notification daemon is running:
|
||||
|
||||
```bash
|
||||
# Check for notification daemon
|
||||
ps aux | grep -i notify
|
||||
|
||||
# Start notification daemon
|
||||
# Depends on your DE: dunst, mako, etc.
|
||||
```
|
||||
|
||||
### Speech Not Working
|
||||
|
||||
Check if speech synthesis is working:
|
||||
|
||||
```bash
|
||||
# Test speech
|
||||
spd-say "Hello"
|
||||
|
||||
# Check if speech-dispatcher is running
|
||||
ps aux | grep speech-dispatcher
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,245 @@
|
||||
# rofi-project-opener
|
||||
|
||||
A Rofi-based project directory launcher for quickly opening projects in your terminal with custom commands.
|
||||
|
||||
## Description
|
||||
|
||||
rofi-project-opener scans configured base directories for project subdirectories and presents them in a Rofi menu. When a project is selected, it opens a terminal, navigates to the project directory, and runs a configurable command (defaults to `opencode`).
|
||||
|
||||
Key features:
|
||||
- JSON-based configuration for project directories
|
||||
- Per-directory custom arguments (e.g., `--agent chiron` for AI coding assistants)
|
||||
- Placeholder support (`%s` for path, `%a` for args) in custom commands
|
||||
- Works with any terminal emulator
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
rofi-project-opener
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.rofi-project-opener
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#rofi-project-opener
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Launch rofi project selector
|
||||
rofi-project-opener
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Read project directories from `~/.config/rofi-project-opener/projects.json`
|
||||
2. Scan each directory for subdirectories (non-hidden)
|
||||
3. Display projects in Rofi for fuzzy selection
|
||||
4. Open terminal, cd to project, and run the configured command
|
||||
|
||||
### Configuration Files
|
||||
|
||||
The script uses two configuration files in `~/.config/rofi-project-opener/`:
|
||||
|
||||
**projects.json** - Project directories with optional args:
|
||||
```json
|
||||
{
|
||||
"nixpkgs": {"path": "~/p/NIX", "args": ""},
|
||||
"chat": {"path": "~/p/CHAT", "args": "--agent chiron"},
|
||||
"dev": {"path": "~/dev", "args": ""}
|
||||
}
|
||||
```
|
||||
|
||||
**config** - Terminal and Rofi settings:
|
||||
```bash
|
||||
TERMINAL="/path/to/kitty"
|
||||
TERMINAL_CMD="opencode %a"
|
||||
ROFI_PROMPT="Select project"
|
||||
ROFI_ARGS="-dmenu -i"
|
||||
```
|
||||
|
||||
### Placeholders
|
||||
|
||||
When using `terminalCommand`, these placeholders are available:
|
||||
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
| `%s` | Full path to selected project |
|
||||
| `%a` | Args from projectDirs config |
|
||||
|
||||
**Examples:**
|
||||
```nix
|
||||
terminalCommand = "opencode %a"; # opencode with project args
|
||||
terminalCommand = "nvim"; # just nvim, no args
|
||||
terminalCommand = "code %s"; # vscode with explicit path
|
||||
terminalCommand = "myapp --dir %s %a"; # custom app with both
|
||||
```
|
||||
|
||||
## Home Manager Module
|
||||
|
||||
### Enable Module
|
||||
|
||||
```nix
|
||||
{config, pkgs, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
cli.rofi-project-opener = {
|
||||
enable = true;
|
||||
projectDirs = {
|
||||
nixpkgs = { path = "~/p/NIX"; };
|
||||
chat = { path = "~/p/CHAT"; args = "--agent chiron"; };
|
||||
dev = { path = "~/dev"; };
|
||||
};
|
||||
terminal = pkgs.kitty;
|
||||
terminalCommand = "opencode %a";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
#### `cli.rofi-project-opener.enable`
|
||||
|
||||
Enable the rofi-project-opener module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
#### `cli.rofi-project-opener.projectDirs`
|
||||
|
||||
Attribute set of base directories to scan for projects.
|
||||
|
||||
- Type: `attrsOf (submodule { path, args })`
|
||||
- Default: `{ dev = { path = "~/dev"; }; projects = { path = "~/projects"; }; }`
|
||||
|
||||
Each entry supports:
|
||||
- `path` (required): Base directory path
|
||||
- `args` (optional): Arguments to pass to the command
|
||||
|
||||
#### `cli.rofi-project-opener.terminal`
|
||||
|
||||
Terminal emulator to use.
|
||||
|
||||
- Type: `either str package`
|
||||
- Default: `"kitty"`
|
||||
|
||||
#### `cli.rofi-project-opener.terminalCommand`
|
||||
|
||||
Command to run in the terminal. Supports `%s` (path) and `%a` (args) placeholders.
|
||||
|
||||
- Type: `str`
|
||||
- Default: `""` (runs `opencode %a`)
|
||||
|
||||
#### `cli.rofi-project-opener.rofiPrompt`
|
||||
|
||||
Prompt text displayed in Rofi.
|
||||
|
||||
- Type: `str`
|
||||
- Default: `"Select project"`
|
||||
|
||||
#### `cli.rofi-project-opener.rofiArgs`
|
||||
|
||||
Arguments to pass to Rofi.
|
||||
|
||||
- Type: `listOf str`
|
||||
- Default: `["-dmenu" "-i"]`
|
||||
|
||||
## Requirements
|
||||
|
||||
### System Requirements
|
||||
|
||||
- Linux with Rofi installed
|
||||
- A terminal emulator (kitty, alacritty, etc.)
|
||||
- jq (included as dependency)
|
||||
|
||||
### Runtime Dependencies
|
||||
|
||||
These are automatically included:
|
||||
- rofi
|
||||
- jq
|
||||
- coreutils
|
||||
- gnugrep
|
||||
- gnused
|
||||
- libnotify
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary)
|
||||
- macOS (not tested)
|
||||
- Windows (not supported)
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Type**: Bash script
|
||||
- **License**: MIT
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Projects Found
|
||||
|
||||
Check that your project directories exist and contain subdirectories:
|
||||
|
||||
```bash
|
||||
cat ~/.config/rofi-project-opener/projects.json
|
||||
ls ~/p/NIX # Should show subdirectories
|
||||
```
|
||||
|
||||
### Args Not Being Passed
|
||||
|
||||
Make sure you're using `%a` placeholder in your `terminalCommand`:
|
||||
|
||||
```nix
|
||||
# Wrong - args not passed
|
||||
terminalCommand = "opencode";
|
||||
|
||||
# Correct - args passed via placeholder
|
||||
terminalCommand = "opencode %a";
|
||||
|
||||
# Also correct - empty uses default behavior with args
|
||||
terminalCommand = "";
|
||||
```
|
||||
|
||||
### Rofi Not Showing
|
||||
|
||||
Ensure Rofi is installed and working:
|
||||
|
||||
```bash
|
||||
echo -e "item1\nitem2" | rofi -dmenu
|
||||
```
|
||||
|
||||
### Terminal Not Opening
|
||||
|
||||
Check terminal configuration:
|
||||
|
||||
```nix
|
||||
# Using package
|
||||
terminal = pkgs.kitty;
|
||||
|
||||
# Using string (must be in PATH)
|
||||
terminal = "kitty";
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [rofi-project-opener Module](../modules/home-manager/cli/rofi-project-opener.md) - Home Manager module documentation
|
||||
- [zellij-ps](./zellij-ps.md) - Similar project switcher for Zellij
|
||||
- [Using Modules](../guides/using-modules.md) - How to use modules
|
||||
@@ -0,0 +1,134 @@
|
||||
# sidecar
|
||||
|
||||
A companion tool for CLI coding agents, providing diffs, file trees, conversation history, and task management with td integration.
|
||||
|
||||
## Description
|
||||
|
||||
sidecar is a terminal UI tool designed to enhance the experience of using AI coding agents in the terminal. It provides a side panel interface for viewing diffs, file trees, conversation history, and integrates with `td` for task management across coding sessions.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔀 **Diff Viewer**: Visual diff display for code changes
|
||||
- 📁 **File Tree**: Navigate and understand project structure
|
||||
- 💬 **Conversation History**: Review and search past AI interactions
|
||||
- ✅ **Task Management**: Integrated with `td` for tracking tasks
|
||||
- 🖥️ **Terminal UI**: Clean interface using tmux panes
|
||||
- 🤖 **AI Agent Integration**: Designed to work with opencode and similar CLI agents
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
sidecar
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.sidecar
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#sidecar
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting sidecar
|
||||
|
||||
```bash
|
||||
# Start sidecar alongside your AI coding agent
|
||||
sidecar
|
||||
|
||||
# Start with a specific agent
|
||||
sidecar --agent opencode
|
||||
```
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Show help
|
||||
sidecar --help
|
||||
|
||||
# Check version
|
||||
sidecar --version
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
sidecar is packaged with the following runtime dependencies:
|
||||
|
||||
- **opencode**: AI coding agent
|
||||
- **td**: Task tracking CLI
|
||||
- **tmux**: Terminal multiplexer for UI layout
|
||||
|
||||
These are automatically included in the PATH when running sidecar.
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### Typical Session
|
||||
|
||||
1. Start `sidecar` in your project directory
|
||||
2. The tool opens a tmux session with panes for:
|
||||
- Your AI coding agent (opencode)
|
||||
- Task list (via td)
|
||||
- Diff viewer
|
||||
- File tree navigator
|
||||
3. Work with your AI agent as usual
|
||||
4. View diffs and changes in real-time
|
||||
5. Track tasks using the integrated td panel
|
||||
|
||||
### With opencode
|
||||
|
||||
```bash
|
||||
# sidecar automatically integrates with opencode
|
||||
cd your-project
|
||||
sidecar
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `SIDECAR_CONFIG`: Custom configuration file path
|
||||
- `SIDECAR_AGENT`: Default AI agent to use (default: opencode)
|
||||
|
||||
### Customization
|
||||
|
||||
Configuration is managed through sidecar's own config system. See the upstream documentation for details.
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.71.1
|
||||
- **Language**: Go
|
||||
- **License**: MIT
|
||||
- **Source**: [GitHub](https://github.com/marcus/sidecar)
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux
|
||||
- macOS (Unix systems)
|
||||
|
||||
## Notes
|
||||
|
||||
- Tests are disabled in the Nix package build
|
||||
- The package wraps the binary with required dependencies (opencode, td, tmux) in PATH
|
||||
- Version check is enabled for the Nix package
|
||||
|
||||
## Related
|
||||
|
||||
- [td](./td.md) - Task tracking CLI used by sidecar
|
||||
- [opencode](./opencode.md) - AI coding agent integration
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,202 @@
|
||||
# stt-ptt
|
||||
|
||||
Push to Talk Speech to Text using Whisper.
|
||||
|
||||
## Description
|
||||
|
||||
stt-ptt is a simple push-to-talk speech-to-text tool that uses whisper.cpp for transcription. It records audio via PipeWire, transcribes it using a local Whisper model, and types the result using wtype (Wayland).
|
||||
|
||||
## Features
|
||||
|
||||
- **Push to Talk**: Start/stop recording with simple commands
|
||||
- **Local Processing**: Uses whisper.cpp for fast, offline transcription
|
||||
- **Wayland Native**: Types transcribed text using wtype
|
||||
- **Configurable**: Model path and notification timeout via environment variables
|
||||
- **Lightweight**: Minimal dependencies, no cloud services
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Home Manager Module (Recommended)
|
||||
|
||||
See [stt-ptt Home Manager Module](../modules/home-manager/cli/stt-ptt.md) for the recommended setup with automatic model download.
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
home.packages = [pkgs.stt-ptt];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
home.packages = [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.stt-ptt
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Start recording
|
||||
stt-ptt start
|
||||
|
||||
# Stop recording and transcribe
|
||||
stt-ptt stop
|
||||
```
|
||||
|
||||
### Keybinding Setup
|
||||
|
||||
The tool is designed to be bound to a key (e.g., hold to record, release to transcribe).
|
||||
|
||||
#### Hyprland
|
||||
|
||||
```nix
|
||||
# In your Hyprland config
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
bind = [
|
||||
# Press Super+V to start, release to stop and transcribe
|
||||
"SUPER, V, exec, stt-ptt start"
|
||||
];
|
||||
bindr = [
|
||||
# Release trigger
|
||||
"SUPER, V, exec, stt-ptt stop"
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
Or in `hyprland.conf`:
|
||||
|
||||
```conf
|
||||
bind = SUPER, V, exec, stt-ptt start
|
||||
bindr = SUPER, V, exec, stt-ptt stop
|
||||
```
|
||||
|
||||
#### Sway
|
||||
|
||||
```conf
|
||||
# Hold to record, release to transcribe
|
||||
bindsym --no-repeat $mod+v exec stt-ptt start
|
||||
bindsym --release $mod+v exec stt-ptt stop
|
||||
```
|
||||
|
||||
#### i3 (X11 - requires xdotool instead of wtype)
|
||||
|
||||
Note: stt-ptt uses wtype which is Wayland-only. For X11, you would need to modify the script to use xdotool.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `STT_MODEL` | Path to Whisper model file | `~/.local/share/stt-ptt/models/ggml-large-v3-turbo.bin` |
|
||||
| `STT_NOTIFY_TIMEOUT` | Notification timeout in ms | `3000` |
|
||||
|
||||
## Requirements
|
||||
|
||||
- **whisper-cpp**: Speech recognition engine
|
||||
- **wtype**: Wayland text input (Wayland compositor required)
|
||||
- **libnotify**: Desktop notifications
|
||||
- **pipewire**: Audio recording
|
||||
|
||||
## Model Setup
|
||||
|
||||
Download a Whisper model from [HuggingFace](https://huggingface.co/ggerganov/whisper.cpp/tree/main):
|
||||
|
||||
```bash
|
||||
# Create model directory
|
||||
mkdir -p ~/.local/share/stt-ptt/models
|
||||
|
||||
# Download model (example: large-v3-turbo)
|
||||
curl -L -o ~/.local/share/stt-ptt/models/ggml-large-v3-turbo.bin \
|
||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
|
||||
```
|
||||
|
||||
Or use the Home Manager module which handles this automatically.
|
||||
|
||||
## Available Models
|
||||
|
||||
| Model | Size | Quality | Speed |
|
||||
|-------|------|---------|-------|
|
||||
| `ggml-tiny` / `ggml-tiny.en` | 75MB | Basic | Fastest |
|
||||
| `ggml-base` / `ggml-base.en` | 142MB | Good | Fast |
|
||||
| `ggml-small` / `ggml-small.en` | 466MB | Better | Medium |
|
||||
| `ggml-medium` / `ggml-medium.en` | 1.5GB | High | Slower |
|
||||
| `ggml-large-v3-turbo` | 1.6GB | High | Fast |
|
||||
| `ggml-large-v3` | 2.9GB | Highest | Slowest |
|
||||
|
||||
Models ending in `.en` are English-only and slightly faster for English text.
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux with Wayland (primary)
|
||||
- Requires PipeWire for audio
|
||||
- X11 not supported (wtype is Wayland-only)
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.0
|
||||
- **Type**: Shell script wrapper
|
||||
- **License**: MIT
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Model Not Found
|
||||
|
||||
Error: `Error: Model not found at /path/to/model`
|
||||
|
||||
**Solution**: Download a model or use the Home Manager module:
|
||||
|
||||
```bash
|
||||
curl -L -o ~/.local/share/stt-ptt/models/ggml-large-v3-turbo.bin \
|
||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
|
||||
```
|
||||
|
||||
### No Audio Recorded
|
||||
|
||||
**Solution**: Ensure PipeWire is running:
|
||||
|
||||
```bash
|
||||
systemctl --user status pipewire
|
||||
```
|
||||
|
||||
### Text Not Typed
|
||||
|
||||
**Solution**: Ensure you're on Wayland and wtype has access:
|
||||
|
||||
```bash
|
||||
# Check if running on Wayland
|
||||
echo $XDG_SESSION_TYPE # Should print "wayland"
|
||||
```
|
||||
|
||||
### Slow Transcription
|
||||
|
||||
**Solution**: Use a smaller model or enable GPU acceleration:
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
model = "ggml-base.en"; # Smaller, faster model
|
||||
};
|
||||
```
|
||||
|
||||
Or with GPU acceleration:
|
||||
|
||||
```nix
|
||||
cli.stt-ptt = {
|
||||
enable = true;
|
||||
# Choose one:
|
||||
whisperPackage = pkgs.whisper-cpp-vulkan; # Vulkan (pre-built)
|
||||
# whisperPackage = pkgs.whisper-cpp.override { cudaSupport = true; }; # NVIDIA
|
||||
# whisperPackage = pkgs.whisper-cpp.override { rocmSupport = true; }; # AMD
|
||||
};
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [stt-ptt Home Manager Module](../modules/home-manager/cli/stt-ptt.md) - Module documentation
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
@@ -0,0 +1,130 @@
|
||||
# td
|
||||
|
||||
Minimalist CLI for tracking tasks across AI coding sessions.
|
||||
|
||||
## Description
|
||||
|
||||
td (task daemon) is a lightweight command-line tool designed for tracking tasks during AI-assisted coding sessions. It provides a simple, fast way to manage todos and maintain context across conversations with AI coding agents.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ **Minimal Task Tracking**: Simple, focused task management
|
||||
- 🤖 **AI Session Aware**: Designed to work with AI coding workflows
|
||||
- 📊 **Usage Tracking**: Track session usage and context
|
||||
- 🔄 **Session Continuity**: Resume tasks from previous sessions
|
||||
- 📝 **Git Integration**: Works alongside git workflows
|
||||
- ⚡ **Fast**: Lightweight Go binary with minimal overhead
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
td
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.td
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#td
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting a Session
|
||||
|
||||
```bash
|
||||
# Start a new session and view current tasks
|
||||
td usage --new-session
|
||||
|
||||
# Quick view of current tasks (no session tracking)
|
||||
td usage -q
|
||||
```
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Show version
|
||||
td version
|
||||
|
||||
# View help
|
||||
td --help
|
||||
```
|
||||
|
||||
### Task Management
|
||||
|
||||
td integrates with AI coding workflows to track tasks across sessions. Use it at the start of conversations to establish context:
|
||||
|
||||
```bash
|
||||
# At conversation start (or after /clear)
|
||||
td usage --new-session
|
||||
|
||||
# For subsequent reads within the same session
|
||||
td usage -q
|
||||
```
|
||||
|
||||
## Integration with AI Agents
|
||||
|
||||
td is designed to be used by AI coding agents as part of their workflow:
|
||||
|
||||
1. **Session Start**: Agent reads current tasks with `td usage --new-session`
|
||||
2. **Work Progress**: Tasks are tracked and updated during the session
|
||||
3. **Session End**: State is preserved for the next session
|
||||
|
||||
### Example Integration
|
||||
|
||||
In an AI agent's system prompt or configuration:
|
||||
|
||||
```
|
||||
You must run td usage --new-session at conversation start (or after /clear) to see current work.
|
||||
Use td usage -q for subsequent reads.
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `TD_DATA_DIR`: Custom directory for td data storage
|
||||
|
||||
### Data Storage
|
||||
|
||||
Task data is stored locally in the project or user directory. See upstream documentation for exact storage location.
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.34.0
|
||||
- **Language**: Go
|
||||
- **License**: MIT
|
||||
- **Source**: [GitHub](https://github.com/marcus/td)
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux
|
||||
- macOS (Unix systems)
|
||||
|
||||
## Notes
|
||||
|
||||
- Tests are disabled in the Nix package build due to git worktree operations
|
||||
- Version check is enabled for the Nix package (`td version`)
|
||||
- Minimal dependencies - pure Go binary
|
||||
|
||||
## Related
|
||||
|
||||
- [sidecar](./sidecar.md) - Uses td for integrated task management
|
||||
- [opencode](./opencode.md) - AI coding agent that integrates with td
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,248 @@
|
||||
# tuxedo-backlight
|
||||
|
||||
Keyboard backlight control for Tuxedo laptops.
|
||||
|
||||
## Description
|
||||
|
||||
A shell script that sets up RGB keyboard backlight colors for Tuxedo laptops with customizable colors for different key groups.
|
||||
|
||||
## Features
|
||||
|
||||
- ⌨️ **RGB Backlight**: Full RGB keyboard backlight support
|
||||
- 🎨 **Color Groups**: Different colors for different key groups
|
||||
- 🔤 **Key Highlighting**: Special colors for modifier keys
|
||||
- 🎯 **One-Command Setup**: Apply all colors with single command
|
||||
- ⚡ **Fast**: Direct sysfs control
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
tuxedo-backlight
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.tuxedo-backlight
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#tuxedo-backlight
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Apply default color scheme
|
||||
tuxedo-backlight
|
||||
```
|
||||
|
||||
### Colors
|
||||
|
||||
The script applies these colors by default:
|
||||
|
||||
| Key Group | Color (RGB) | Description |
|
||||
|-----------|-------------|-------------|
|
||||
| Main keys | `0 150 255` | Blue (Cyan-ish) |
|
||||
| Function keys (F1-F12) | `0 255 80` | Green (Lime) |
|
||||
| Arrow keys | `0 255 80` | Green (Lime) |
|
||||
| Numpad area | `255 150 0` | Orange |
|
||||
| DEL key | `255 0 155` | Pink/Magenta |
|
||||
| ESC key | `255 0 155` | Pink/Magenta |
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify Colors
|
||||
|
||||
Edit the script to customize colors:
|
||||
|
||||
```nix
|
||||
# In pkgs/tuxedo-backlight/default.nix
|
||||
|
||||
# All keys
|
||||
echo 'R G B' | tee /sys/class/leds/rgb:kbd_backlight*/multi_intensity
|
||||
|
||||
# Specific key (e.g., ESC)
|
||||
echo 'R G B' | tee /sys/class/leds/rgb:kbd_backlight/multi_intensity
|
||||
```
|
||||
|
||||
RGB format: `Red Green Blue` (0-255 each)
|
||||
|
||||
### Color Examples
|
||||
|
||||
| Color | RGB Value |
|
||||
|--------|-----------|
|
||||
| Red | `255 0 0` |
|
||||
| Green | `0 255 0` |
|
||||
| Blue | `0 0 255` |
|
||||
| Cyan | `0 255 255` |
|
||||
| Magenta | `255 0 255` |
|
||||
| Yellow | `255 255 0` |
|
||||
| White | `255 255 255` |
|
||||
| Orange | `255 150 0` |
|
||||
| Purple | `150 0 255` |
|
||||
|
||||
## Automatic Startup
|
||||
|
||||
### Systemd Service
|
||||
|
||||
Create `/etc/systemd/system/tuxedo-backlight.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Tuxedo Keyboard Backlight
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/run/current-system/sw/bin/tuxedo-backlight
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable tuxedo-backlight.service
|
||||
sudo systemctl start tuxedo-backlight.service
|
||||
```
|
||||
|
||||
### NixOS Configuration
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
# Run at boot
|
||||
systemd.services.tuxedo-backlight = {
|
||||
description = "Set Tuxedo keyboard backlight";
|
||||
after = ["multi-user.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.tuxedo-backlight}/bin/tuxedo-backlight";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Hardware
|
||||
|
||||
- Tuxedo laptop with RGB keyboard backlight
|
||||
- Linux kernel with appropriate driver support
|
||||
|
||||
### System Requirements
|
||||
|
||||
- Linux (Tuxedo laptops)
|
||||
- Write access to `/sys/class/leds/`
|
||||
|
||||
### Permissions
|
||||
|
||||
The script requires write access to sysfs:
|
||||
|
||||
```bash
|
||||
# Check permissions
|
||||
ls -la /sys/class/leds/rgb:kbd_backlight*
|
||||
|
||||
# If permissions are needed
|
||||
sudo tuxedo-backlight
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (Tuxedo laptops only)
|
||||
- macOS (not supported)
|
||||
- Windows (not supported)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Such Device
|
||||
|
||||
Error: `No such file or directory`
|
||||
|
||||
**Solution**: Ensure you're on a Tuxedo laptop with RGB keyboard:
|
||||
|
||||
```bash
|
||||
# Check if RGB backlight exists
|
||||
ls -la /sys/class/leds/rgb:kbd_backlight*
|
||||
```
|
||||
|
||||
### Permission Denied
|
||||
|
||||
Error: `Permission denied`
|
||||
|
||||
**Solution**: Run with sudo or configure udev rules:
|
||||
|
||||
```bash
|
||||
# Run with sudo
|
||||
sudo tuxedo-backlight
|
||||
|
||||
# Or create udev rule for user access
|
||||
sudo vim /etc/udev/rules.d/99-tuxedo-backlight.rules
|
||||
```
|
||||
|
||||
udev rule:
|
||||
|
||||
```
|
||||
SUBSYSTEM=="leds", ATTR{brightness}=="*", ACTION=="add", RUN+="/usr/bin/chgrp -R input /sys/class/leds/rgb:*"
|
||||
SUBSYSTEM=="leds", ATTR{brightness}=="*", ACTION=="add", RUN+="/usr/bin/chmod -R g+w /sys/class/leds/rgb:*"
|
||||
```
|
||||
|
||||
### Colors Not Applied
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check RGB backlight is supported:
|
||||
|
||||
```bash
|
||||
cat /sys/class/leds/rgb:kbd_backlight*/multi_intensity
|
||||
```
|
||||
|
||||
2. Ensure driver is loaded:
|
||||
|
||||
```bash
|
||||
# Check for Tuxedo drivers
|
||||
lsmod | grep tuxedo
|
||||
|
||||
# Load if needed
|
||||
sudo modprobe tuxedo_keyboard
|
||||
```
|
||||
|
||||
## Key Layout Reference
|
||||
|
||||
The script sets colors for these key groups:
|
||||
|
||||
### Key Numbers
|
||||
|
||||
- **All keys**: Main keyboard area (except special keys)
|
||||
- **15**: DEL key
|
||||
- **No number**: ESC key
|
||||
- **1-12, 102**: Function keys (F1-F12, Fn)
|
||||
- **16-19, 36-39, 56-59, 76-79, 96-99, 117-119**: Numpad and keys above numpad
|
||||
- **95, 114-116**: Arrow keys
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.0
|
||||
- **Type**: Shell script
|
||||
- **License**: MIT
|
||||
|
||||
## Related
|
||||
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,204 @@
|
||||
# zellij-ps
|
||||
|
||||
A Zellij project switcher for quickly navigating and opening project workspaces.
|
||||
|
||||
## Description
|
||||
|
||||
zellij-ps is a Fish script inspired by ThePrimeagen's tmux-sessionizer. It provides a fast, interactive way to switch between project folders in Zellij. Using `fd` for fast directory discovery and `fzf` for fuzzy selection, it helps you quickly jump into your work.
|
||||
|
||||
The script searches through your configured project folders (`$PROJECT_FOLDERS`) and either creates a new Zellij session for the selected project or attaches to an existing one.
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Overlay
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
zellij-ps
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Reference
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.m3ta-nixpkgs.packages.${pkgs.system}.zellij-ps
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Run Directly
|
||||
|
||||
```bash
|
||||
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#zellij-ps
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Run from outside Zellij to start a project session
|
||||
zellij-ps
|
||||
|
||||
# Or pass a project path directly
|
||||
zellij-ps ~/projects/my-project
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Search through your `$PROJECT_FOLDERS` for directories
|
||||
2. Open fzf for fuzzy project selection (if no argument provided)
|
||||
3. Create a new Zellij session or attach to existing one for the selected project
|
||||
|
||||
### Configuration
|
||||
|
||||
Set your project folders in your shell configuration:
|
||||
|
||||
**Fish example:**
|
||||
```fish
|
||||
set -x PROJECT_FOLDERS ~/projects:~/code:~/work
|
||||
```
|
||||
|
||||
**Bash/Zsh example:**
|
||||
```bash
|
||||
export PROJECT_FOLDERS="$HOME/projects:$HOME/code:$HOME/work"
|
||||
```
|
||||
|
||||
Folders should be delimited by `:` and can include `~` for home directory.
|
||||
|
||||
## Home Manager Module
|
||||
|
||||
### Enable Module
|
||||
|
||||
```nix
|
||||
{config, ...}: {
|
||||
imports = [m3ta-nixpkgs.homeManagerModules.default];
|
||||
|
||||
cli.zellij-ps = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
#### `cli.zellij-ps.enable`
|
||||
|
||||
Enable the zellij-ps module.
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
#### `cli.zellij-ps.package`
|
||||
|
||||
Custom package to use.
|
||||
|
||||
- Type: `package`
|
||||
- Default: `pkgs.zellij-ps`
|
||||
|
||||
## Requirements
|
||||
|
||||
### System Requirements
|
||||
|
||||
- Linux or Unix-like system
|
||||
- Configured `$PROJECT_FOLDERS` environment variable
|
||||
|
||||
## Platform Support
|
||||
|
||||
- Linux (primary)
|
||||
- macOS (may work)
|
||||
- Windows (not supported)
|
||||
|
||||
## Build Information
|
||||
|
||||
- **Version**: 0.1.0
|
||||
- **Type**: Fish script
|
||||
- **License**: MIT
|
||||
- **Inspired by**: [ThePrimeagen's tmux-sessionizer](https://github.com/ThePrimeagen/.dotfiles/blob/master/bin/.local/scripts/tmux-sessionizer)
|
||||
- **Source**: [Gitea](https://code.m3ta.dev/m3tam3re/helper-scripts)
|
||||
|
||||
## Source Code
|
||||
|
||||
The script is available at:
|
||||
|
||||
```
|
||||
https://code.m3ta.dev/m3tam3re/helper-scripts/src/branch/main/zellij-ps.fish
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Projects Found
|
||||
|
||||
If fzf shows no results, check your `$PROJECT_FOLDERS` variable:
|
||||
|
||||
```bash
|
||||
# In fish
|
||||
echo $PROJECT_FOLDERS
|
||||
|
||||
# In bash/zsh
|
||||
echo $PROJECT_FOLDERS
|
||||
```
|
||||
|
||||
Ensure the folders exist and contain subdirectories.
|
||||
|
||||
### Already in Zellij Session
|
||||
|
||||
If you're already inside a Zellij session, you'll see:
|
||||
|
||||
```
|
||||
You are in a Zellij Session!
|
||||
Please use the session manager to switch sessions.
|
||||
```
|
||||
|
||||
Use Zellij's built-in session manager (`Ctrl+p` → `s`) to switch sessions instead.
|
||||
|
||||
### fd Not Found
|
||||
|
||||
Error: `fd: command not found`
|
||||
|
||||
**Solution**: Ensure fd is installed:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
fd
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Fish Not Found
|
||||
|
||||
Error: `fish: command not found`
|
||||
|
||||
**Solution**: Ensure Fish is installed:
|
||||
|
||||
```nix
|
||||
{pkgs, ...}: {
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### fzf Not Working
|
||||
|
||||
Ensure fzf is installed and configured:
|
||||
|
||||
```bash
|
||||
# Check fzf
|
||||
which fzf
|
||||
|
||||
# Test fzf
|
||||
echo -e "item1\nitem2\nitem3" | fzf
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [zellij-ps Module](../modules/home-manager/cli/zellij-ps.md) - Home Manager module documentation
|
||||
- [Using Modules](../guides/using-modules.md) - How to use modules
|
||||
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
|
||||
- [Quick Start](../QUICKSTART.md) - Getting started guide
|
||||
@@ -0,0 +1,637 @@
|
||||
# m3ta-nixpkgs: Cleanup & Improvements Plan
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Address 10 issues identified in codebase review — reduce duplication, improve naming consistency, extract inline scripts, add testing, and update documentation.
|
||||
|
||||
**Architecture:** Incremental improvements across lib/, modules/, overlays/, docs/, and CI. Each change is self-contained and can be merged independently. No breaking changes to public API (backward-compat aliases preserved where needed).
|
||||
|
||||
**Repo:** `gitea@code.m3ta.dev:m3tam3re/nixpkgs.git` (master branch)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Deduplication & Naming (Low Risk)
|
||||
|
||||
### Task 1: Remove duplicate opencode-rules.nix file
|
||||
|
||||
**Objective:** Eliminate the duplicate file import. The `coding-rules.nix` is the canonical source; `opencode-rules.nix` is an identical copy. Make the alias a one-liner in `lib/default.nix`.
|
||||
|
||||
**Files:**
|
||||
- Delete: `lib/opencode-rules.nix`
|
||||
- Modify: `lib/default.nix`
|
||||
|
||||
**Step 1: Update lib/default.nix to alias directly**
|
||||
|
||||
```nix
|
||||
{lib}: {
|
||||
ports = import ./ports.nix {inherit lib;};
|
||||
|
||||
coding-rules = import ./coding-rules.nix {inherit lib;};
|
||||
|
||||
# Backward-compat alias: opencode-rules → coding-rules
|
||||
opencode-rules = import ./coding-rules.nix {inherit lib;};
|
||||
opencode = import ./coding-rules.nix {inherit lib;};
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Delete the duplicate file**
|
||||
|
||||
```bash
|
||||
git rm lib/opencode-rules.nix
|
||||
```
|
||||
|
||||
**Step 3: Verify nothing breaks**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "refactor: remove duplicate opencode-rules.nix, use alias in default.nix"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Tool-agnostic naming in coding-rules.nix internals
|
||||
|
||||
**Objective:** Rename internal variables and output artifacts in `coding-rules.nix` from opencode-specific names to generic names, while keeping the backward-compat alias `mkOpencodeRules`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/coding-rules.nix`
|
||||
|
||||
**Step 1: Rename internal symbols**
|
||||
|
||||
In `lib/coding-rules.nix`, rename:
|
||||
- `rulesDir` stays `.opencode-rules` (this is a filesystem path used by existing projects, changing it would break)
|
||||
- `opencodeConfig` → `rulesConfig`
|
||||
- `opencode.json` output → `coding-rules.json` (add a comment noting it was renamed)
|
||||
- Add `rulesDir` option to function signature with default `.opencode-rules`
|
||||
|
||||
Updated function:
|
||||
|
||||
```nix
|
||||
{lib}: let
|
||||
mkCodingRules = {
|
||||
agents,
|
||||
languages ? [],
|
||||
concerns ? [
|
||||
"coding-style"
|
||||
"naming"
|
||||
"documentation"
|
||||
"testing"
|
||||
"git-workflow"
|
||||
"project-structure"
|
||||
],
|
||||
frameworks ? [],
|
||||
extraInstructions ? [],
|
||||
rulesDir ? ".opencode-rules",
|
||||
}: let
|
||||
instructions =
|
||||
(map (c: "${rulesDir}/concerns/${c}.md") concerns)
|
||||
++ (map (l: "${rulesDir}/languages/${l}.md") languages)
|
||||
++ (map (f: "${rulesDir}/frameworks/${f}.md") frameworks)
|
||||
++ extraInstructions;
|
||||
|
||||
rulesConfig = {
|
||||
"$schema" = "https://opencode.ai/config.json";
|
||||
inherit instructions;
|
||||
};
|
||||
in {
|
||||
inherit instructions;
|
||||
|
||||
shellHook = ''
|
||||
# Create/update symlink to AGENTS rules directory
|
||||
ln -sfn ${agents}/rules ${rulesDir}
|
||||
|
||||
# Generate coding-rules configuration file
|
||||
cat > coding-rules.json <<'RULES_EOF'
|
||||
${builtins.toJSON rulesConfig}
|
||||
RULES_EOF
|
||||
'';
|
||||
};
|
||||
|
||||
# Backward-compat alias
|
||||
mkOpencodeRules = mkCodingRules;
|
||||
in {
|
||||
inherit mkCodingRules mkOpencodeRules;
|
||||
};
|
||||
```
|
||||
|
||||
**Step 2: Update shellHook comment in AGENTS.md**
|
||||
|
||||
In `AGENTS.md`, update the coding-rules section to mention the new `rulesDir` parameter and the `coding-rules.json` output file.
|
||||
|
||||
**Step 3: Verify**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "refactor: tool-agnostic naming in coding-rules.nix internals"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Remove redundant overlays entry in flake.nix
|
||||
|
||||
**Objective:** The `default` and `additions` overlays in `flake.nix` produce identical output. Remove `additions` if not referenced elsewhere, or document why both exist.
|
||||
|
||||
**Files:**
|
||||
- Modify: `flake.nix`
|
||||
- Check: all consumer repos for references to `overlays.additions`
|
||||
|
||||
**Step 1: Search for consumers of overlays.additions**
|
||||
|
||||
```bash
|
||||
# Check nixos-config and other repos
|
||||
grep -r "overlays.additions" /data/.hermes/repos/nixos-config/
|
||||
grep -r "additions" /data/.hermes/repos/nixos-config/ --include="*.nix" | grep overlay
|
||||
```
|
||||
|
||||
**Step 2: If no consumers found, remove additions**
|
||||
|
||||
In `flake.nix`, simplify overlays to:
|
||||
|
||||
```nix
|
||||
overlays = {
|
||||
default = final: prev:
|
||||
import ./pkgs {
|
||||
pkgs = final;
|
||||
inputs = inputs;
|
||||
};
|
||||
|
||||
modifications = final: prev: import ./overlays/mods {inherit prev;};
|
||||
};
|
||||
```
|
||||
|
||||
**Step 3: Verify**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "refactor: remove redundant 'additions' overlay (identical to 'default')"
|
||||
```
|
||||
|
||||
**Note:** If `additions` IS used elsewhere, add a comment explaining the convention and skip this task.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Extract Inline Scripts (Medium Risk)
|
||||
|
||||
### Task 4: Extract pi-agent runner script to standalone file
|
||||
|
||||
**Objective:** Move the ~200-line inline bash script in `modules/nixos/pi-agent.nix` (the `runner` variable) to a separate file `modules/nixos/pi-agent-runner.sh` that gets imported via `builtins.readFile` + `pkgs.writeShellApplication`.
|
||||
|
||||
**Files:**
|
||||
- Create: `modules/nixos/pi-agent-runner.sh`
|
||||
- Modify: `modules/nixos/pi-agent.nix`
|
||||
|
||||
**Step 1: Create the runner script file**
|
||||
|
||||
Extract the body of the `runner` script (everything inside the `pkgs.writeShellScriptBin cfg.wrapper.runnerName '' ... ''`) into `modules/nixos/pi-agent-runner.sh`.
|
||||
|
||||
The script uses Nix-style variable interpolation (`${...}`). We need to keep Nix template variables as `${...}` and convert runtime bash variables to use `$` prefix. Since the script already uses Nix `escapeShellArg` and `escapeShellArg` calls, the cleanest approach is:
|
||||
|
||||
Create `modules/nixos/pi-agent-runner.sh` as a template that `pkgs.substituteAll` or `builtins.readFile` + string replacement can process. However, given the heavy Nix interpolation, the pragmatic approach is to use `pkgs.writeShellApplication` with the script body inline but extracted to a `let` binding:
|
||||
|
||||
```nix
|
||||
# In pi-agent.nix, replace the inline runner with:
|
||||
let
|
||||
runnerScript = builtins.readFile ./pi-agent-runner.sh;
|
||||
# ... or keep as let binding but move the body to a separate derivation
|
||||
```
|
||||
|
||||
**Important caveat:** The script has ~30 Nix variable interpolations (`${cfg.user}`, `${escapeShellArg ...}`, etc.). Full extraction to a .sh file would require either:
|
||||
- (a) `substituteAll` with `--replace` for each variable — unwieldy at 30+ substitutions
|
||||
- (b) Converting to env vars passed at runtime — cleaner but changes security posture
|
||||
- (c) Keeping the Nix interpolation but extracting to a `let` block in a separate `.nix` file
|
||||
|
||||
**Recommended approach: Option (c)** — Create `modules/nixos/pi-agent-runner.nix` as a function that takes `cfg` and returns the script:
|
||||
|
||||
```nix
|
||||
# modules/nixos/pi-agent-runner.nix
|
||||
{cfg, pkgs, lib, ...}:
|
||||
with lib; let
|
||||
# ... all the helper variables from pi-agent.nix ...
|
||||
in
|
||||
pkgs.writeShellScriptBin cfg.wrapper.runnerName ''
|
||||
# ... the script body ...
|
||||
'';
|
||||
```
|
||||
|
||||
Then in `pi-agent.nix`:
|
||||
```nix
|
||||
runner = import ./pi-agent-runner.nix {inherit cfg pkgs lib;};
|
||||
```
|
||||
|
||||
**Step 2: Similarly extract the wrapper script**
|
||||
|
||||
Create `modules/nixos/pi-agent-wrapper.nix` for the `wrapper` variable.
|
||||
|
||||
**Step 3: Verify**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
# Also test in a nixos-rebuild if possible
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add modules/nixos/pi-agent-runner.nix modules/nixos/pi-agent-wrapper.nix
|
||||
git commit -m "refactor: extract pi-agent runner and wrapper to separate files"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Testing (Higher Value)
|
||||
|
||||
### Task 5: Add basic lib function tests
|
||||
|
||||
**Objective:** Add `nix eval`-based tests for `lib/agents.nix` parseRule logic and `lib/coding-rules.nix` instruction generation.
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/lib/agents-test.nix`
|
||||
- Create: `tests/lib/coding-rules-test.nix`
|
||||
- Modify: `flake.nix` (add checks)
|
||||
|
||||
**Step 1: Create test infrastructure**
|
||||
|
||||
```nix
|
||||
# tests/lib/default.nix
|
||||
{
|
||||
agents = import ./agents-test.nix;
|
||||
coding-rules = import ./coding-rules-test.nix;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Write agents.nix parseRule test**
|
||||
|
||||
```nix
|
||||
# tests/lib/agents-test.nix
|
||||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
agentsLib = (import ../../lib {inherit lib;}).agents;
|
||||
|
||||
# Test parseRule helper
|
||||
test1 = let
|
||||
result = builtins.tryEval (
|
||||
let
|
||||
# We can't directly test parseRule since it's internal.
|
||||
# Instead, test the renderer with minimal input.
|
||||
canonical = {
|
||||
test-agent = {
|
||||
description = "Test agent";
|
||||
mode = "primary";
|
||||
systemPrompt = "You are a test.";
|
||||
permissions = {
|
||||
bash = { intent = "allow"; };
|
||||
edit = { intent = "ask"; rules = ["rm -rf *:deny"]; };
|
||||
};
|
||||
};
|
||||
};
|
||||
pkgs = import <nixpkgs> { system = "x86_64-linux"; };
|
||||
rendered = agentsLib.renderForOpencode {
|
||||
inherit pkgs canonical;
|
||||
};
|
||||
in
|
||||
# Verify the derivation builds
|
||||
builtins.pathExists "${rendered}/test-agent.md"
|
||||
);
|
||||
in assert result.value == true; true;
|
||||
|
||||
in {
|
||||
parseRule-basic = test1;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Write coding-rules test**
|
||||
|
||||
```nix
|
||||
# tests/lib/coding-rules-test.nix
|
||||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
codingRulesLib = (import ../../lib {inherit lib;}).coding-rules;
|
||||
|
||||
rules = codingRulesLib.mkCodingRules {
|
||||
agents = "/tmp/fake-agents";
|
||||
languages = ["python"];
|
||||
concerns = ["naming"];
|
||||
rulesDir = ".coding-rules";
|
||||
};
|
||||
|
||||
# Verify instructions are generated correctly
|
||||
test1 = assert rules.instructions == [
|
||||
".coding-rules/concerns/naming.md"
|
||||
".coding-rules/languages/python.md"
|
||||
]; true;
|
||||
|
||||
# Verify backward-compat alias exists
|
||||
test2 = assert codingRulesLib.mkOpencodeRules == codingRulesLib.mkCodingRules; true;
|
||||
|
||||
in {
|
||||
instructions-correct = test1;
|
||||
backward-compat = test2;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Add to flake.nix checks**
|
||||
|
||||
In `flake.nix`, extend the `checks` attribute:
|
||||
|
||||
```nix
|
||||
checks = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
packages = import ./pkgs {inherit pkgs inputs;};
|
||||
in
|
||||
builtins.mapAttrs (name: pkg: pkgs.lib.hydraJob pkg) packages
|
||||
// {
|
||||
formatting = pkgs.runCommand "check-formatting" {} ''
|
||||
${pkgs.alejandra}/bin/alejandra --check ${./.}
|
||||
touch $out
|
||||
'';
|
||||
lib-tests = pkgs.runCommand "lib-tests" {} ''
|
||||
${pkgs.nix}/bin/nix-instantiate --eval ${./tests/lib/default.nix}
|
||||
touch $out
|
||||
'';
|
||||
});
|
||||
```
|
||||
|
||||
**Step 5: Verify**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/
|
||||
git commit -m "test: add basic lib function tests for agents and coding-rules"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Add NixOS VM test for pi-agent module
|
||||
|
||||
**Objective:** Add a basic NixOS VM test that verifies the pi-agent module can be evaluated and the wrapper/runner scripts exist.
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/nixos/pi-agent-test.nix`
|
||||
- Modify: `flake.nix` (add to checks)
|
||||
|
||||
**Step 1: Write the VM test**
|
||||
|
||||
```nix
|
||||
# tests/nixos/pi-agent-test.nix
|
||||
{pkgs, ...}: {
|
||||
name = "pi-agent";
|
||||
|
||||
nodes.machine = {config, ...}: {
|
||||
imports = [
|
||||
${(pkgs.path + "/nixos/modules/module-list.nix")}
|
||||
];
|
||||
|
||||
# Minimal pi-agent config
|
||||
m3ta.pi-agent = {
|
||||
enable = true;
|
||||
package = pkgs.writeScriptBin "pi-agent" ''
|
||||
#!/bin/sh
|
||||
echo "pi-agent mock"
|
||||
'';
|
||||
createUser = true;
|
||||
hostUsers = {
|
||||
testuser = {
|
||||
projectRoots = ["/tmp/test-project"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.testuser = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
# Verify user was created
|
||||
machine.succeed("id pi-agent")
|
||||
|
||||
# Verify wrapper exists
|
||||
machine.succeed("which pi")
|
||||
|
||||
# Verify state directory
|
||||
machine.succeed("test -d /var/lib/pi-agent")
|
||||
machine.succeed("test -d /var/lib/pi-agent/.pi")
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Add to flake.nix checks**
|
||||
|
||||
```nix
|
||||
# In the checks attrset:
|
||||
pi-agent-vm-test = pkgs.nixosTest (import ./tests/nixos/pi-agent-test.nix {inherit pkgs;});
|
||||
```
|
||||
|
||||
**Step 3: Verify**
|
||||
|
||||
```bash
|
||||
nix build .#checks.x86_64-linux.pi-agent-vm-test
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/nixos/
|
||||
git commit -m "test: add NixOS VM test for pi-agent module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Documentation (Low Risk, High Value)
|
||||
|
||||
### Task 7: Update AGENTS.md to reflect current state
|
||||
|
||||
**Objective:** Remove outdated migration sections, update function signatures, and align with current code.
|
||||
|
||||
**Files:**
|
||||
- Modify: `AGENTS.md`
|
||||
|
||||
**Step 1: Update the AGENTS REWORK migration section**
|
||||
|
||||
The section starting with `## MIGRATION: Agent System (OpenCode → Canonical TOML)` describes a completed migration. Convert it to a brief "Architecture" section that describes the current state, not the migration path.
|
||||
|
||||
**Step 2: Update lib.agents function table**
|
||||
|
||||
Verify that the function signatures and descriptions in the AGENTS.md table match the actual functions in `lib/agents.nix`. Specifically:
|
||||
- `loadCanonical` takes `{agentsInput}` — confirm docs match
|
||||
- `renderForPi` now has `primaryAgent` parameter — confirm documented
|
||||
- `shellHookForTool` exists — confirm documented
|
||||
|
||||
**Step 3: Update coding-rules documentation**
|
||||
|
||||
Replace references to `mkOpencodeRules` with `mkCodingRules` as primary, `mkOpencodeRules` as backward-compat alias. Document the new `rulesDir` parameter.
|
||||
|
||||
**Step 4: Update overlay documentation**
|
||||
|
||||
Remove or annotate the `additions` overlay depending on Task 3 outcome.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "docs: update AGENTS.md to reflect current codebase state"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Add CHANGELOG.md
|
||||
|
||||
**Objective:** Create a changelog that captures recent work (from git log) so consumers can track changes.
|
||||
|
||||
**Files:**
|
||||
- Create: `CHANGELOG.md`
|
||||
|
||||
**Step 1: Generate changelog from git history**
|
||||
|
||||
```bash
|
||||
cd /data/.hermes/repos/nixpkgs-review
|
||||
git log --oneline --no-merges master | head -30
|
||||
```
|
||||
|
||||
**Step 2: Write CHANGELOG.md**
|
||||
|
||||
Structure as Keep a Changelog format:
|
||||
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.4.0] - 2026-04-15
|
||||
|
||||
### Added
|
||||
- Pi agent wrapper with per-host-user policy enforcement (`m3ta.pi-agent` NixOS module)
|
||||
- `coding.agents.pi` Home Manager module with settings, MCP, and skills support
|
||||
- `coding.agents.claude-code` Home Manager module with MCP integration
|
||||
- Automated package updates via Gitea Actions (`nix-update` workflow)
|
||||
- `lib.agents.renderForPi` with primaryAgent selection and pi-subagents format
|
||||
- `pkgs/td` - Task management CLI for AI coding sessions
|
||||
|
||||
### Changed
|
||||
- Renamed `lib.opencode-rules` → `lib.coding-rules` (backward-compat alias preserved)
|
||||
- Agent system migrated to harness-agnostic canonical format
|
||||
- Pi settings sync now merges host and Nix-managed values via deep_merge
|
||||
|
||||
### Fixed
|
||||
- Pi settings sync race condition on first run
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "docs: add CHANGELOG.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Minor Cleanups (Low Risk)
|
||||
|
||||
### Task 9: Clean up pkgs/default.nix unused `system` binding
|
||||
|
||||
**Objective:** The `system = pkgs.stdenv.hostPlatform.system;` binding in `pkgs/default.nix` is only used for the two input-pass-throughs. If those are the only consumers, it's fine, but add a clarifying comment.
|
||||
|
||||
**Files:**
|
||||
- Modify: `pkgs/default.nix`
|
||||
|
||||
**Step 1: Add clarifying comment**
|
||||
|
||||
```nix
|
||||
{
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}: let
|
||||
# Only used for flake input pass-throughs below
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
in {
|
||||
...
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "docs: clarify system binding in pkgs/default.nix"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Remove commented-out overlay entries in overlays/default.nix
|
||||
|
||||
**Objective:** Clean up the large block of commented-out code in `overlays/default.nix` (nodejs_24, paperless-ngx, anytype-heart, hyprpanel, etc.). These belong in git history, not in active code.
|
||||
|
||||
**Files:**
|
||||
- Modify: `overlays/default.nix`
|
||||
|
||||
**Step 1: Remove commented-out blocks**
|
||||
|
||||
Remove:
|
||||
- The `rose-pine-hyprcursor` addition from `additions` (if it's unused — check with grep)
|
||||
- The commented-out `nodejs_24`, `paperless-ngx`, `anytype-heart`, `trezord`, `mesa`, `hyprpanel` blocks from `modifications`
|
||||
- The commented-out overlay inputs (`temp-packages`, `stable-packages`, `pinned-packages`, `locked-packages`, `master-packages`) if they reference inputs not in `flake.nix`
|
||||
|
||||
Actually, `nixpkgs-stable`, `nixpkgs-9e9486b`, `nixpkgs-9472de4`, `nixpkgs-locked`, `nixpkgs-master` are NOT in the current `flake.nix` inputs. These overlays will fail if referenced. They should either be removed or the inputs should be added.
|
||||
|
||||
**Action:**
|
||||
- Keep `master-packages` IF `nixpkgs-master` is in flake.nix inputs (it IS — good)
|
||||
- Remove `temp-packages`, `pinned-packages`, `locked-packages` (inputs don't exist)
|
||||
- Keep `stable-packages` IF `nixpkgs-stable` exists in inputs (check — it does NOT currently exist)
|
||||
- Keep `additions` with `rose-pine-hyprcursor` IF `rose-pine-hyprcursor` input exists (check)
|
||||
|
||||
**Step 2: Verify**
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "chore: remove dead overlay entries for non-existent flake inputs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Order & Priority
|
||||
|
||||
| Task | Risk | Effort | Impact | Dependencies |
|
||||
|------|------|--------|--------|-------------|
|
||||
| T1: Remove opencode-rules.nix | Low | 5min | Clean | None |
|
||||
| T2: Tool-agnostic naming | Low | 15min | Consistency | None |
|
||||
| T3: Remove redundant overlay | Low | 10min | Clean | Check consumers |
|
||||
| T9: Clarify system binding | Low | 2min | Docs | None |
|
||||
| T10: Remove dead overlays | Low | 10min | Clean | None |
|
||||
| T7: Update AGENTS.md | Low | 20min | Docs | After T1, T2 |
|
||||
| T8: Add CHANGELOG.md | Low | 15min | Docs | None |
|
||||
| T4: Extract pi-agent scripts | Medium | 45min | Maintainability | None |
|
||||
| T5: Lib function tests | Medium | 30min | Quality | None |
|
||||
| T6: NixOS VM test | Medium | 45min | Quality | None |
|
||||
|
||||
**Recommended order:** T1 → T9 → T10 → T3 → T2 → T7 → T8 → T5 → T4 → T6
|
||||
|
||||
**Branching strategy:** Create a feature branch `chore/cleanup-review` from master, implement all tasks, open PR for review before merging.
|
||||
@@ -0,0 +1,272 @@
|
||||
# Library Functions
|
||||
|
||||
Documentation for library functions available in m3ta-nixpkgs.
|
||||
|
||||
## Overview
|
||||
|
||||
The library provides helper functions for your NixOS and Home Manager configurations.
|
||||
|
||||
## Available Libraries
|
||||
|
||||
### `m3ta-lib.ports`
|
||||
|
||||
Port management utilities for managing service ports across hosts.
|
||||
|
||||
## Port Management Functions
|
||||
|
||||
### `mkPortHelpers`
|
||||
|
||||
Create port helper functions from a ports configuration.
|
||||
|
||||
#### Signature
|
||||
|
||||
```nix
|
||||
mkPortHelpers :: portsConfig -> portHelpers
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
`portsConfig` - An attribute set with structure:
|
||||
|
||||
```nix
|
||||
{
|
||||
ports = { service-name = port-number; ... };
|
||||
hostPorts = { hostname = { service-name = port-number; ... }; ... };
|
||||
}
|
||||
```
|
||||
|
||||
#### Returns
|
||||
|
||||
An attribute set containing helper functions:
|
||||
|
||||
```nix
|
||||
{
|
||||
getPort = service: host -> port-number-or-null;
|
||||
getHostPorts = host -> ports-attrs;
|
||||
listServices = -> [string];
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
```nix
|
||||
{config, inputs, ...}: let
|
||||
m3taLib = inputs.m3ta-nixpkgs.lib.${config.system};
|
||||
|
||||
myPorts = {
|
||||
ports = {
|
||||
nginx = 80;
|
||||
grafana = 3000;
|
||||
};
|
||||
hostPorts = {
|
||||
laptop = {
|
||||
nginx = 8080;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
portHelpers = m3taLib.ports.mkPortHelpers myPorts;
|
||||
in {
|
||||
# Get port with host override
|
||||
services.nginx.port = portHelpers.getPort "nginx" config.networking.hostName;
|
||||
|
||||
# Get all ports for host
|
||||
laptopPorts = portHelpers.getHostPorts "laptop";
|
||||
|
||||
# List all services
|
||||
allServices = portHelpers.listServices;
|
||||
}
|
||||
```
|
||||
|
||||
### `getPort` (from portHelpers)
|
||||
|
||||
Get port for a service, with optional host-specific override.
|
||||
|
||||
#### Signature
|
||||
|
||||
```nix
|
||||
getPort :: string -> string -> int-or-null
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
1. `service` - The service name (string)
|
||||
2. `host` - The hostname (string), or `null` for default
|
||||
|
||||
#### Returns
|
||||
|
||||
Port number (int) or `null` if service not found.
|
||||
|
||||
#### Usage
|
||||
|
||||
```nix
|
||||
services.nginx = {
|
||||
port = portHelpers.getPort "nginx" "laptop"; # Returns host-specific port
|
||||
# or
|
||||
port = portHelpers.getPort "nginx" null; # Returns default port
|
||||
};
|
||||
```
|
||||
|
||||
### `getHostPorts` (from portHelpers)
|
||||
|
||||
Get all ports for a specific host (merges defaults with host overrides).
|
||||
|
||||
#### Signature
|
||||
|
||||
```nix
|
||||
getHostPorts :: string -> attrs
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
1. `host` - The hostname (string)
|
||||
|
||||
#### Returns
|
||||
|
||||
Attribute set of all ports for the host.
|
||||
|
||||
#### Usage
|
||||
|
||||
```nix
|
||||
laptopPorts = portHelpers.getHostPorts "laptop";
|
||||
# Returns: { nginx = 8080; grafana = 3000; prometheus = 9090; ... }
|
||||
```
|
||||
|
||||
### `listServices` (from portHelpers)
|
||||
|
||||
List all defined service names.
|
||||
|
||||
#### Signature
|
||||
|
||||
```nix
|
||||
listServices :: -> [string]
|
||||
```
|
||||
|
||||
#### Returns
|
||||
|
||||
List of service names (strings).
|
||||
|
||||
#### Usage
|
||||
|
||||
```nix
|
||||
allServices = portHelpers.listServices;
|
||||
# Returns: ["nginx" "grafana" "prometheus" "homepage"]
|
||||
```
|
||||
|
||||
### `getDefaultPort`
|
||||
|
||||
Simple helper to get a port without host override.
|
||||
|
||||
#### Signature
|
||||
|
||||
```nix
|
||||
getDefaultPort :: portsConfig -> string -> int-or-null
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
1. `portsConfig` - Same structure as `mkPortHelpers`
|
||||
2. `service` - The service name (string)
|
||||
|
||||
#### Returns
|
||||
|
||||
Port number (int) or `null` if service not found.
|
||||
|
||||
#### Usage
|
||||
|
||||
```nix
|
||||
services.my-service = {
|
||||
port = m3taLib.ports.getDefaultPort myPorts "my-service";
|
||||
};
|
||||
```
|
||||
|
||||
## Using Library Functions
|
||||
|
||||
### Importing
|
||||
|
||||
```nix
|
||||
{config, inputs, ...}: let
|
||||
# Import library
|
||||
m3taLib = inputs.m3ta-nixpkgs.lib.${config.system};
|
||||
in {
|
||||
# Use library functions
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Custom Port Management
|
||||
|
||||
```nix
|
||||
{config, inputs, ...}: let
|
||||
m3taLib = inputs.m3ta-nixpkgs.lib.${config.system};
|
||||
|
||||
myPorts = {
|
||||
ports = {
|
||||
web = 80;
|
||||
api = 8080;
|
||||
db = 5432;
|
||||
};
|
||||
hostPorts = {
|
||||
dev = {
|
||||
web = 8080;
|
||||
api = 8081;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
portHelpers = m3taLib.ports.mkPortHelpers myPorts;
|
||||
|
||||
hostname = config.networking.hostName;
|
||||
in {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts.${hostname} = {
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:${toString (portHelpers.getPort "api" hostname)}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
port = portHelpers.getPort "db" hostname;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Generate Config Files
|
||||
|
||||
```nix
|
||||
{inputs, ...}: let
|
||||
m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||
|
||||
myPorts = {
|
||||
ports = {
|
||||
service1 = 3000;
|
||||
service2 = 3001;
|
||||
};
|
||||
};
|
||||
|
||||
portHelpers = m3taLib.ports.mkPortHelpers myPorts;
|
||||
in {
|
||||
environment.etc."ports.toml".text = generators.toTOML {} {
|
||||
services = portHelpers.getHostPorts "desktop";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Function Reference Summary
|
||||
|
||||
| Function | Purpose | Return Type |
|
||||
|----------|---------|------------|
|
||||
| `mkPortHelpers` | Create port helper functions | `portHelpers` attrs |
|
||||
| `getPort` | Get port with optional host override | `int or null` |
|
||||
| `getHostPorts` | Get all ports for host | `attrs` |
|
||||
| `listServices` | List all service names | `[string]` |
|
||||
| `getDefaultPort` | Get default port only | `int or null` |
|
||||
|
||||
## Related
|
||||
|
||||
- [Port Management Guide](../guides/port-management.md) - Detailed usage guide
|
||||
- [NixOS Ports Module](../modules/nixos/ports.md) - Port management module
|
||||
- [Home Manager Ports Module](../modules/home-manager/ports.md) - User-level port management
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding library functions
|
||||
@@ -0,0 +1,498 @@
|
||||
# Code Patterns and Anti-Patterns
|
||||
|
||||
Common code patterns and anti-patterns used in m3ta-nixpkgs.
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines recommended patterns and common pitfalls when working with m3ta-nixpkgs.
|
||||
|
||||
## Code Patterns
|
||||
|
||||
### Package Pattern
|
||||
|
||||
#### CallPackage Registry
|
||||
|
||||
Use `callPackage` for lazy evaluation:
|
||||
|
||||
```nix
|
||||
# Good - pkgs/default.nix
|
||||
{
|
||||
inherit (pkgs) callPackage;
|
||||
} rec {
|
||||
code2prompt = callPackage ./code2prompt {};
|
||||
mem0 = callPackage ./mem0 {};
|
||||
}
|
||||
```
|
||||
|
||||
#### Meta Fields
|
||||
|
||||
Always include complete `meta` information:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
meta = with lib; {
|
||||
description = "My awesome package";
|
||||
homepage = "https://github.com/author/package";
|
||||
changelog = "https://github.com/author/package/releases/tag/v${version}";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "program-name";
|
||||
};
|
||||
```
|
||||
|
||||
### Module Pattern
|
||||
|
||||
#### Standard Module Structure
|
||||
|
||||
```nix
|
||||
# Good
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib; let
|
||||
cfg = config.m3ta.myModule;
|
||||
in {
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
# ... options
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# ... configuration
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### mkEnableOption
|
||||
|
||||
Always use `mkEnableOption` for enable flags:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
};
|
||||
|
||||
# Bad
|
||||
options.m3ta.myModule.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
```
|
||||
|
||||
#### Conditional Configuration
|
||||
|
||||
Use `mkIf` for conditional config:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
config = mkIf cfg.enable {
|
||||
services.my-service.enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
#### Multiple Conditions
|
||||
|
||||
Use `mkMerge` for multiple conditions:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
config = mkMerge [
|
||||
(mkIf cfg.feature1.enable {
|
||||
# config for feature1
|
||||
})
|
||||
(mkIf cfg.feature2.enable {
|
||||
# config for feature2
|
||||
})
|
||||
];
|
||||
```
|
||||
|
||||
### Import Pattern
|
||||
|
||||
#### Multi-line Imports
|
||||
|
||||
Multi-line, trailing commas:
|
||||
|
||||
```nix
|
||||
# Good
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
```
|
||||
|
||||
#### Explicit Dependencies
|
||||
|
||||
```nix
|
||||
# Good
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
openssl,
|
||||
pkg-config,
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
buildInputs = [openssl pkg-config];
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### lib.fakeHash in Commits
|
||||
|
||||
**Bad**: Committing `lib.fakeHash`
|
||||
|
||||
```nix
|
||||
# Bad - Never commit this!
|
||||
src = fetchFromGitHub {
|
||||
hash = lib.fakeHash;
|
||||
};
|
||||
```
|
||||
|
||||
**Solution**: Build to get real hash:
|
||||
|
||||
```bash
|
||||
nix build .#your-package
|
||||
# Copy actual hash from error message
|
||||
```
|
||||
|
||||
### Flat Module Files
|
||||
|
||||
**Bad**: All modules in one file
|
||||
|
||||
```nix
|
||||
# Bad - Hard to maintain
|
||||
{config, lib, pkgs, ...}: {
|
||||
options.m3ta.cli = {
|
||||
tool1 = mkEnableOption "tool1";
|
||||
tool2 = mkEnableOption "tool2";
|
||||
# ... many more
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf config.cli.tool1.enable {...})
|
||||
(mkIf config.cli.tool2.enable {...})
|
||||
# ... many more
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Organize by category
|
||||
|
||||
```nix
|
||||
# Good - modules/home-manager/cli/
|
||||
# modules/home-manager/cli/default.nix
|
||||
{
|
||||
imports = [
|
||||
./tool1.nix
|
||||
./tool2.nix
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Hardcoded Ports
|
||||
|
||||
**Bad**: Hardcoding ports in services
|
||||
|
||||
```nix
|
||||
# Bad
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen 80;
|
||||
}
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
**Solution**: Use port management
|
||||
|
||||
```nix
|
||||
# Good
|
||||
m3ta.ports = {
|
||||
enable = true;
|
||||
definitions = {nginx = 80;};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
httpConfig = ''
|
||||
server {
|
||||
listen ${toString (config.m3ta.ports.get "nginx")};
|
||||
}
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
### Skipping Meta Fields
|
||||
|
||||
**Bad**: Incomplete meta information
|
||||
|
||||
```nix
|
||||
# Bad
|
||||
meta = {
|
||||
description = "My package";
|
||||
};
|
||||
```
|
||||
|
||||
**Solution**: Include all fields
|
||||
|
||||
```nix
|
||||
# Good
|
||||
meta = with lib; {
|
||||
description = "My awesome package";
|
||||
homepage = "https://github.com/author/package";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "program-name";
|
||||
};
|
||||
```
|
||||
|
||||
### with pkgs; in Modules
|
||||
|
||||
**Bad**: Using `with pkgs;` at module level
|
||||
|
||||
```nix
|
||||
# Bad
|
||||
{config, lib, pkgs, ...}: with pkgs; {
|
||||
config.environment.systemPackages = [
|
||||
vim
|
||||
git
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Explicit package references or limited with
|
||||
|
||||
```nix
|
||||
# Good - Explicit references
|
||||
{config, lib, pkgs, ...}: {
|
||||
config.environment.systemPackages = with pkgs; [
|
||||
vim
|
||||
git
|
||||
];
|
||||
}
|
||||
|
||||
# Or - Full references
|
||||
{config, lib, pkgs, ...}: {
|
||||
config.environment.systemPackages = [
|
||||
pkgs.vim
|
||||
pkgs.git
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Orphaned Package Directories
|
||||
|
||||
**Bad**: Creating directory without registering
|
||||
|
||||
```nix
|
||||
# Bad - Package not visible
|
||||
# pkgs/my-package/default.nix exists
|
||||
# But pkgs/default.nix doesn't reference it
|
||||
```
|
||||
|
||||
**Solution**: Register in `pkgs/default.nix`
|
||||
|
||||
```nix
|
||||
# Good
|
||||
{
|
||||
inherit (pkgs) callPackage;
|
||||
} rec {
|
||||
my-package = callPackage ./my-package {};
|
||||
}
|
||||
```
|
||||
|
||||
## Type Safety
|
||||
|
||||
### No Type Suppression
|
||||
|
||||
**Bad**: Using `as any`
|
||||
|
||||
```nix
|
||||
# Bad
|
||||
let
|
||||
value = someFunction config as any;
|
||||
in
|
||||
# ...
|
||||
```
|
||||
|
||||
**Solution**: Fix underlying type issues
|
||||
|
||||
```nix
|
||||
# Good
|
||||
let
|
||||
value = someFunction config;
|
||||
in
|
||||
# Ensure value has correct type
|
||||
```
|
||||
|
||||
### Proper Type Definitions
|
||||
|
||||
```nix
|
||||
# Good
|
||||
options.m3ta.myModule = {
|
||||
enable = mkEnableOption "my module";
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = "Port to run on";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Package Names
|
||||
|
||||
**Good**: `lowercase-hyphen`
|
||||
|
||||
```nix
|
||||
code2prompt
|
||||
hyprpaper-random
|
||||
launch-webapp
|
||||
```
|
||||
|
||||
**Bad**: CamelCase or underscores
|
||||
|
||||
```nix
|
||||
code2Prompt # Bad
|
||||
hyprpaper_random # Bad
|
||||
```
|
||||
|
||||
### Variables
|
||||
|
||||
**Good**: `camelCase`
|
||||
|
||||
```nix
|
||||
portHelpers
|
||||
configFile
|
||||
serviceName
|
||||
```
|
||||
|
||||
**Bad**: Snake_case or kebab-case
|
||||
|
||||
```nix
|
||||
port_helpers # Bad
|
||||
port-helpers # Bad
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
**Good**: `m3ta.*` namespace for m3ta modules, `cli.*` namespace for CLI modules
|
||||
|
||||
```nix
|
||||
m3ta.ports.enable = true;
|
||||
cli.zellij-ps.enable = true;
|
||||
```
|
||||
|
||||
**Bad**: Flat namespace
|
||||
|
||||
```nix
|
||||
ports.enable = true; # Potential conflict
|
||||
zellij-ps.enable = true; # Hard to find
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Lazy Evaluation
|
||||
|
||||
**Good**: Use `callPackage`
|
||||
|
||||
```nix
|
||||
# Good - Only builds requested package
|
||||
code2prompt = callPackage ./code2prompt {};
|
||||
```
|
||||
|
||||
**Bad**: Building all packages
|
||||
|
||||
```nix
|
||||
# Bad - Builds everything even if not used
|
||||
code2prompt = import ./code2prompt {};
|
||||
```
|
||||
|
||||
### Selective Imports
|
||||
|
||||
**Good**: Import only needed modules
|
||||
|
||||
```nix
|
||||
# Good
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.mem0
|
||||
];
|
||||
```
|
||||
|
||||
**Bad**: Importing all modules
|
||||
|
||||
```nix
|
||||
# Bad - Imports and evaluates all modules
|
||||
imports = [
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
];
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### No Secrets in Store
|
||||
|
||||
**Bad**: Putting secrets in configuration
|
||||
|
||||
```nix
|
||||
# Bad - Secret in Nix store
|
||||
m3ta.mem0.llm.apiKey = "sk-xxx";
|
||||
```
|
||||
|
||||
**Solution**: Use secret files
|
||||
|
||||
```nix
|
||||
# Good - Secret from file
|
||||
m3ta.mem0.llm.apiKeyFile = "/run/secrets/openai-api-key";
|
||||
```
|
||||
|
||||
### Proper User/Group
|
||||
|
||||
**Good**: Dedicated users for services
|
||||
|
||||
```nix
|
||||
# Good
|
||||
users.users.mem0 = {
|
||||
isSystemUser = true;
|
||||
group = "mem0";
|
||||
};
|
||||
```
|
||||
|
||||
### Service Hardening
|
||||
|
||||
**Good**: Enable systemd hardening
|
||||
|
||||
```nix
|
||||
# Good
|
||||
systemd.services.mem0.serviceConfig = {
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
| Practice | Do | Don't |
|
||||
|----------|-----|--------|
|
||||
| Hash fetching | Get real hash from build error | Commit `lib.fakeHash` |
|
||||
| Module organization | Categorize by function | Put all in one file |
|
||||
| Port management | Use `m3ta.ports` module | Hardcode ports |
|
||||
| Meta fields | Include all fields | Skip fields |
|
||||
| Type safety | Fix type errors | Use `as any` |
|
||||
| Dependencies | Explicit declarations | Implicit deps |
|
||||
| Imports | Multi-line, trailing comma | Single-line, no comma |
|
||||
| Naming | Follow conventions | Mix styles |
|
||||
| Secrets | Use file-based | Put in config |
|
||||
| Evaluation | Lazy (`callPackage`) | Import everything |
|
||||
|
||||
## Related
|
||||
|
||||
- [Contributing Guide](../CONTRIBUTING.md) - Code style and guidelines
|
||||
- [Architecture](../ARCHITECTURE.md) - Understanding repository structure
|
||||
- [Adding Packages](../guides/adding-packages.md) - Package creation patterns
|
||||
@@ -0,0 +1,193 @@
|
||||
# Example Standalone Home Manager Configuration using m3ta-nixpkgs
|
||||
# This file demonstrates how to use m3ta-nixpkgs with standalone Home Manager
|
||||
# (without NixOS)
|
||||
{
|
||||
description = "Example Home Manager configuration with m3ta-nixpkgs";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# Add m3ta-nixpkgs as an input
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
# Or use a local path during development:
|
||||
# url = "path:/home/user/projects/m3ta-nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
m3ta-nixpkgs,
|
||||
...
|
||||
} @ inputs: let
|
||||
system = "x86_64-linux"; # Change to your system: aarch64-linux, x86_64-darwin, aarch64-darwin
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
# Apply m3ta-nixpkgs overlays
|
||||
overlays = [
|
||||
m3ta-nixpkgs.overlays.default
|
||||
];
|
||||
};
|
||||
in {
|
||||
homeConfigurations = {
|
||||
# Replace 'm3tam3re' with your actual username
|
||||
m3tam3re = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
|
||||
# Pass inputs as extra special args
|
||||
extraSpecialArgs = {inherit inputs;};
|
||||
|
||||
modules = [
|
||||
# Import m3ta's Home Manager modules
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
|
||||
# Main Home Manager configuration
|
||||
{
|
||||
# ============================================
|
||||
# User Information
|
||||
# ============================================
|
||||
home.username = "m3tam3re";
|
||||
home.homeDirectory = "/home/m3tam3re";
|
||||
home.stateVersion = "24.05";
|
||||
|
||||
# ============================================
|
||||
# Packages from m3ta-nixpkgs
|
||||
# ============================================
|
||||
# Since we applied the overlay above, packages are available directly
|
||||
home.packages = with pkgs; [
|
||||
# Custom packages from m3ta-nixpkgs
|
||||
code2prompt
|
||||
hyprpaper-random
|
||||
launch-webapp
|
||||
msty-studio
|
||||
pomodoro-timer
|
||||
zellij-ps
|
||||
|
||||
# Regular packages from nixpkgs
|
||||
git
|
||||
vim
|
||||
htop
|
||||
fzf
|
||||
ripgrep
|
||||
];
|
||||
|
||||
# ============================================
|
||||
# Custom Home Manager Modules
|
||||
# ============================================
|
||||
# If you've defined custom Home Manager modules
|
||||
# programs.myProgram = {
|
||||
# enable = true;
|
||||
# package = pkgs.myProgram;
|
||||
# settings = {
|
||||
# theme = "dark";
|
||||
# };
|
||||
# };
|
||||
|
||||
# ============================================
|
||||
# Shell Configuration
|
||||
# ============================================
|
||||
programs.bash = {
|
||||
enable = true;
|
||||
shellAliases = {
|
||||
ll = "ls -l";
|
||||
".." = "cd ..";
|
||||
};
|
||||
};
|
||||
|
||||
programs.zsh = {
|
||||
enable = true;
|
||||
enableCompletion = true;
|
||||
autosuggestion.enable = true;
|
||||
syntaxHighlighting.enable = true;
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# Git Configuration
|
||||
# ============================================
|
||||
programs.git = {
|
||||
enable = true;
|
||||
userName = "Your Name";
|
||||
userEmail = "your.email@example.com";
|
||||
extraConfig = {
|
||||
init.defaultBranch = "main";
|
||||
pull.rebase = false;
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# Additional Programs
|
||||
# ============================================
|
||||
programs.direnv = {
|
||||
enable = true;
|
||||
nix-direnv.enable = true;
|
||||
};
|
||||
|
||||
programs.fzf = {
|
||||
enable = true;
|
||||
enableBashIntegration = true;
|
||||
enableZshIntegration = true;
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# Environment Variables
|
||||
# ============================================
|
||||
home.sessionVariables = {
|
||||
EDITOR = "vim";
|
||||
VISUAL = "vim";
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# File Management
|
||||
# ============================================
|
||||
# Create custom config files
|
||||
home.file.".config/my-app/config.json".text = ''
|
||||
{
|
||||
"setting": "value"
|
||||
}
|
||||
'';
|
||||
|
||||
# ============================================
|
||||
# Allow Home Manager to manage itself
|
||||
# ============================================
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# Minimal Example Configuration
|
||||
# ============================================
|
||||
minimal = home-manager.lib.homeManagerConfiguration {
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [m3ta-nixpkgs.overlays.default];
|
||||
};
|
||||
|
||||
modules = [
|
||||
{
|
||||
home.username = "m3tam3re";
|
||||
home.homeDirectory = "/home/m3tam3re";
|
||||
home.stateVersion = "24.05";
|
||||
|
||||
# Just use a couple of custom packages
|
||||
home.packages = with pkgs; [
|
||||
code2prompt
|
||||
zellij-ps
|
||||
];
|
||||
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
# Example NixOS Configuration using m3ta-nixpkgs
|
||||
# This file demonstrates how to integrate m3ta-nixpkgs into your NixOS system
|
||||
{
|
||||
description = "Example NixOS configuration with m3ta-nixpkgs";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
# Add m3ta-nixpkgs as an input
|
||||
m3ta-nixpkgs = {
|
||||
url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs";
|
||||
# Or use a local path during development:
|
||||
# url = "path:/home/user/projects/m3ta-nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
m3ta-nixpkgs,
|
||||
home-manager,
|
||||
...
|
||||
} @ inputs: {
|
||||
nixosConfigurations = {
|
||||
# Replace 'hostname' with your actual hostname
|
||||
hostname = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
|
||||
specialArgs = {inherit inputs;};
|
||||
|
||||
modules = [
|
||||
# Your hardware configuration
|
||||
./hardware-configuration.nix
|
||||
|
||||
# Import m3ta's NixOS modules (if any are defined)
|
||||
m3ta-nixpkgs.nixosModules.default
|
||||
|
||||
# Main configuration
|
||||
({pkgs, ...}: {
|
||||
# ============================================
|
||||
# METHOD 1: Using Overlays (Recommended)
|
||||
# ============================================
|
||||
# This makes custom packages available as if they were in nixpkgs
|
||||
nixpkgs.overlays = [
|
||||
m3ta-nixpkgs.overlays.default
|
||||
# Or use individual overlays for more control:
|
||||
# m3ta-nixpkgs.overlays.additions
|
||||
# m3ta-nixpkgs.overlays.modifications
|
||||
];
|
||||
|
||||
# Now you can use packages normally
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Custom packages from m3ta-nixpkgs
|
||||
code2prompt
|
||||
hyprpaper-random
|
||||
msty-studio
|
||||
pomodoro-timer
|
||||
tuxedo-backlight
|
||||
zellij-ps
|
||||
|
||||
# Regular nixpkgs packages
|
||||
vim
|
||||
git
|
||||
htop
|
||||
];
|
||||
|
||||
# ============================================
|
||||
# METHOD 2: Direct Package Reference
|
||||
# ============================================
|
||||
# Use this if you don't want to use overlays
|
||||
# environment.systemPackages = [
|
||||
# inputs.m3ta-nixpkgs.packages.${pkgs.system}.code2prompt
|
||||
# inputs.m3ta-nixpkgs.packages.${pkgs.system}.zellij-ps
|
||||
# ];
|
||||
|
||||
# ============================================
|
||||
# Using Custom NixOS Modules
|
||||
# ============================================
|
||||
# If you've defined custom NixOS modules, configure them here
|
||||
# m3ta.myModule = {
|
||||
# enable = true;
|
||||
# # module-specific options
|
||||
# };
|
||||
|
||||
# ============================================
|
||||
# System Configuration
|
||||
# ============================================
|
||||
system.stateVersion = "24.05";
|
||||
networking.hostName = "hostname";
|
||||
|
||||
# Enable flakes
|
||||
nix.settings.experimental-features = ["nix-command" "flakes"];
|
||||
})
|
||||
|
||||
# ============================================
|
||||
# Home Manager Integration
|
||||
# ============================================
|
||||
home-manager.nixosModules.home-manager
|
||||
{
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
home-manager.users.m3tam3re = {pkgs, ...}: {
|
||||
# Import m3ta's Home Manager modules
|
||||
imports = [
|
||||
m3ta-nixpkgs.homeManagerModules.default
|
||||
# Or import specific modules:
|
||||
# m3ta-nixpkgs.homeManagerModules.zellij-ps
|
||||
];
|
||||
|
||||
# Home Manager packages with overlay
|
||||
home.packages = with pkgs; [
|
||||
launch-webapp
|
||||
# Other packages...
|
||||
];
|
||||
|
||||
# Configure custom Home Manager modules
|
||||
# programs.myProgram = {
|
||||
# enable = true;
|
||||
# # options...
|
||||
# };
|
||||
|
||||
home.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# ============================================
|
||||
# Alternative: Minimal Configuration
|
||||
# ============================================
|
||||
minimal = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
({pkgs, ...}: {
|
||||
nixpkgs.overlays = [m3ta-nixpkgs.overlays.default];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
code2prompt
|
||||
];
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Generated
+109
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"nodes": {
|
||||
"basecamp": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1774505501,
|
||||
"narHash": "sha256-7UiRrDptj7yuEFwToOfdunUMz/i3jRLR7CmMoYQjq6k=",
|
||||
"owner": "basecamp",
|
||||
"repo": "basecamp-cli",
|
||||
"rev": "f087e6ef84002503d0dbc75ea1c8c928a8928d9e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "basecamp",
|
||||
"ref": "v0.7.2",
|
||||
"repo": "basecamp-cli",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1775423009,
|
||||
"narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-master": {
|
||||
"locked": {
|
||||
"lastModified": 1775657231,
|
||||
"narHash": "sha256-DP8FfybiZPp5WLB9eIk0TC2mdvuYzxLGgrBODDrwPEI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4e03baaa39b7746eac5704d623461422131cd03d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"opencode": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs-master"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775782812,
|
||||
"narHash": "sha256-m+Ue7FWiTjKMAn1QefAwOMfOb2Vybk0mJPV9zcbkOmE=",
|
||||
"owner": "anomalyco",
|
||||
"repo": "opencode",
|
||||
"rev": "877be7e8e04142cd8fbebcb5e6c4b9617bf28cce",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "anomalyco",
|
||||
"ref": "v1.4.3",
|
||||
"repo": "opencode",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"openspec": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775372219,
|
||||
"narHash": "sha256-MJakKC026Sarz7nMmiFrfONWc4xgaw8ApV0Hhp4ebhM=",
|
||||
"owner": "Fission-AI",
|
||||
"repo": "OpenSpec",
|
||||
"rev": "64d476f8b924bb9b74b896ea0aa784970e37da69",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Fission-AI",
|
||||
"repo": "OpenSpec",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"basecamp": "basecamp",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-master": "nixpkgs-master",
|
||||
"opencode": "opencode",
|
||||
"openspec": "openspec"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
description = "m3ta's personal Nix repository - Custom packages, overlays, and modules";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nixpkgs-master.url = "github:NixOS/nixpkgs/master";
|
||||
|
||||
basecamp = {
|
||||
url = "github:basecamp/basecamp-cli/v0.7.2";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# opencode needs newer bun from master
|
||||
opencode = {
|
||||
url = "github:anomalyco/opencode/v1.4.3";
|
||||
inputs.nixpkgs.follows = "nixpkgs-master";
|
||||
};
|
||||
|
||||
# openspec - spec-driven development for AI coding assistants
|
||||
openspec = {
|
||||
url = "github:Fission-AI/OpenSpec";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
...
|
||||
} @ inputs: let
|
||||
# Supported systems
|
||||
systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];
|
||||
|
||||
# Helper function to generate an attrset for each of the systems
|
||||
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||
|
||||
# Helper to create pkgs for a given system
|
||||
pkgsFor = system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
in {
|
||||
# Custom packages - accessible via 'nix build .#package-name'
|
||||
packages = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./pkgs {inherit pkgs inputs;});
|
||||
|
||||
# Overlays - can be imported in your system configuration
|
||||
overlays = {
|
||||
# Default overlay: adds all custom packages
|
||||
default = final: prev:
|
||||
import ./pkgs {
|
||||
pkgs = final;
|
||||
inputs = inputs;
|
||||
};
|
||||
|
||||
modifications = final: prev: import ./overlays/mods {inherit prev;};
|
||||
};
|
||||
|
||||
# NixOS modules - for system-level configuration
|
||||
nixosModules = {
|
||||
default = ./modules/nixos;
|
||||
# Individual modules for selective imports
|
||||
ports = ./modules/nixos/ports.nix;
|
||||
mem0 = ./modules/nixos/mem0.nix;
|
||||
pi-agent = ./modules/nixos/pi-agent.nix;
|
||||
};
|
||||
|
||||
# Home Manager modules - for user-level configuration
|
||||
homeManagerModules = {
|
||||
default = import ./modules/home-manager;
|
||||
ports = import ./modules/home-manager/ports.nix;
|
||||
opencode = import ./modules/home-manager/coding/opencode.nix;
|
||||
agents = import ./modules/home-manager/coding/agents;
|
||||
zellij-ps = import ./modules/home-manager/zellij-ps.nix;
|
||||
};
|
||||
|
||||
# Library functions - helper utilities for your configuration
|
||||
lib = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./lib {lib = pkgs.lib;});
|
||||
|
||||
# Development shells for various programming environments
|
||||
# Usage: nix develop .#<shell-name>
|
||||
# Available shells: default, python, devops, opencode
|
||||
devShells = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
import ./shells {inherit pkgs inputs;});
|
||||
|
||||
# Formatter for 'nix fmt'
|
||||
formatter = forAllSystems (system: (pkgsFor system).alejandra);
|
||||
|
||||
# Checks for 'nix flake check' - verifies all packages build
|
||||
checks = forAllSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
packages = import ./pkgs {inherit pkgs inputs;};
|
||||
in
|
||||
builtins.mapAttrs (name: pkg: pkgs.lib.hydraJob pkg) packages
|
||||
// {
|
||||
formatting = pkgs.runCommand "check-formatting" {} ''
|
||||
${pkgs.alejandra}/bin/alejandra --check ${./.}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
# NixOS VM test for pi-agent module (x86_64-linux only)
|
||||
pi-agent-vm-test =
|
||||
if system == "x86_64-linux"
|
||||
then
|
||||
pkgs.nixosTest (import ./tests/nixos/pi-agent-test.nix {inherit pkgs;})
|
||||
else {};
|
||||
});
|
||||
|
||||
# Templates for creating new packages/modules
|
||||
templates = {
|
||||
package = {
|
||||
path = ./templates/package;
|
||||
description = "Template for a new package";
|
||||
};
|
||||
nixos-module = {
|
||||
path = ./templates/nixos-module;
|
||||
description = "Template for a new NixOS module";
|
||||
};
|
||||
home-manager-module = {
|
||||
path = ./templates/home-manager-module;
|
||||
description = "Template for a new Home Manager module";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
# Harness-agnostic agent management utilities
|
||||
#
|
||||
# This module provides functions to load canonical agent definitions and
|
||||
# render them for different AI coding tools (OpenCode, Claude Code, Pi).
|
||||
#
|
||||
# Usage in your configuration:
|
||||
#
|
||||
# let
|
||||
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||
# canonical = m3taLib.agents.loadCanonical { agentsInput = inputs.agents; };
|
||||
#
|
||||
# # Render for a specific tool
|
||||
# rendered = m3taLib.agents.renderForOpencode {
|
||||
# inherit pkgs canonical;
|
||||
# modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||
# };
|
||||
# in { ... }
|
||||
{lib}: let
|
||||
# ── Shared helpers ─────────────────────────────────────────────
|
||||
# Split a rule string on the LAST colon to get { pattern, action }.
|
||||
# e.g. "rm -rf *:ask" → pattern="rm -rf *", action="ask"
|
||||
# e.g. "/run/agenix/**:deny" → pattern="/run/agenix/**", action="deny"
|
||||
parseRule = ruleStr: let
|
||||
parts = lib.strings.splitString ":" ruleStr;
|
||||
action = lib.last parts;
|
||||
pattern = lib.concatStringsSep ":" (lib.init parts);
|
||||
in {inherit pattern action;};
|
||||
|
||||
agentsLib = {
|
||||
# ── loadCanonical ─────────────────────────────────────────────
|
||||
#
|
||||
# Load canonical agent definitions from the AGENTS flake input.
|
||||
# Returns the canonical attrset from lib.loadAgents (keyed by slug).
|
||||
|
||||
loadCanonical = {agentsInput}: agentsInput.lib.loadAgents;
|
||||
|
||||
# ── OpenCode renderer ─────────────────────────────────────────
|
||||
#
|
||||
# Produces a directory of agent *.md files suitable for
|
||||
# ~/.config/opencode/agents/ (system-level)
|
||||
# .opencode/agents/ (project-level)
|
||||
#
|
||||
# Each file has YAML frontmatter (description, mode, optional model,
|
||||
# optional permission) followed by the agent's systemPrompt content.
|
||||
# The filename (without .md) becomes the agent name in OpenCode.
|
||||
|
||||
renderForOpencode = {
|
||||
pkgs,
|
||||
canonical,
|
||||
modelOverrides ? {},
|
||||
}: let
|
||||
# Render one permission section to YAML lines.
|
||||
# intent-only → single line: " <tool>: <intent>"
|
||||
# intent+rules → nested block
|
||||
renderPermSection = tool: section:
|
||||
if !(section ? rules) || section.rules == []
|
||||
then [" ${tool}: ${section.intent}"]
|
||||
else let
|
||||
parsedRules = map parseRule section.rules;
|
||||
wildcardLine = " \"*\": ${section.intent}";
|
||||
ruleLines = map (r: " \"${r.pattern}\": ${r.action}") parsedRules;
|
||||
in
|
||||
[" ${tool}:"] ++ [wildcardLine] ++ ruleLines;
|
||||
|
||||
renderPermBlock = permissions:
|
||||
if permissions == {} || permissions == null
|
||||
then []
|
||||
else
|
||||
["permission:"]
|
||||
++ lib.concatLists (
|
||||
lib.mapAttrsToList renderPermSection permissions
|
||||
);
|
||||
|
||||
mkFrontmatter = name: agent: let
|
||||
descLine = "description: \"${agent.description}.\"";
|
||||
modeLine = "mode: ${agent.mode}";
|
||||
modelLine =
|
||||
lib.optionalString
|
||||
(modelOverrides ? ${name})
|
||||
"model: ${modelOverrides.${name}}\n";
|
||||
permBlock = renderPermBlock (agent.permissions or {});
|
||||
permLines =
|
||||
if permBlock == []
|
||||
then ""
|
||||
else lib.concatStringsSep "\n" permBlock + "\n";
|
||||
in "---\n${descLine}\n${modeLine}\n${modelLine}${permLines}---\n";
|
||||
|
||||
mkAgentContent = name: agent:
|
||||
(mkFrontmatter name agent) + agent.systemPrompt;
|
||||
|
||||
mkAgentFile = name: agent:
|
||||
pkgs.writeText "${name}.md" (mkAgentContent name agent);
|
||||
|
||||
agentFiles = lib.mapAttrs mkAgentFile canonical;
|
||||
|
||||
copyCommands = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: file: "cp ${file} $out/${name}.md") agentFiles
|
||||
);
|
||||
in
|
||||
pkgs.runCommand "opencode-agents" {} ''
|
||||
mkdir -p $out
|
||||
${copyCommands}
|
||||
'';
|
||||
|
||||
# ── Claude Code renderer ──────────────────────────────────────
|
||||
#
|
||||
# Produces a directory containing:
|
||||
# .claude/agents/<name>.md — one per agent with YAML frontmatter
|
||||
# .claude/settings.json — permission rules in Claude Code DSL
|
||||
#
|
||||
# Claude Code requires:
|
||||
# - name field: [a-z0-9-]+ (kebab-case)
|
||||
# - description field: required
|
||||
# - All agents are subagents (no primary/subagent distinction)
|
||||
|
||||
renderForClaudeCode = {
|
||||
pkgs,
|
||||
canonical,
|
||||
modelOverrides ? {},
|
||||
}: let
|
||||
# Claude Code permission DSL format: "Tool(pattern)" or just "Tool"
|
||||
# Canonical bash rules → "Bash(pattern)" entries
|
||||
# Canonical edit rules → "Edit(pattern)" entries
|
||||
renderPermAllow = permissions: let
|
||||
bashRules =
|
||||
if !(permissions ? bash)
|
||||
then []
|
||||
else if permissions.bash.intent == "allow"
|
||||
then ["Bash"]
|
||||
else
|
||||
map
|
||||
(r: let parsed = parseRule r; in "Bash(${parsed.pattern})")
|
||||
(lib.filter (r: (parseRule r).action == "allow") (permissions.bash.rules or []));
|
||||
editRules =
|
||||
if !(permissions ? edit)
|
||||
then []
|
||||
else if permissions.edit.intent == "allow"
|
||||
then ["Edit"]
|
||||
else
|
||||
map
|
||||
(r: let parsed = parseRule r; in "Edit(${parsed.pattern})")
|
||||
(lib.filter (r: (parseRule r).action == "allow") (permissions.edit.rules or []));
|
||||
webRules =
|
||||
lib.optional (permissions.webfetch.intent or "" == "allow") "WebFetch";
|
||||
in
|
||||
bashRules ++ editRules ++ webRules;
|
||||
|
||||
renderPermDeny = permissions: let
|
||||
bashRules =
|
||||
if !(permissions ? bash)
|
||||
then []
|
||||
else
|
||||
map
|
||||
(r: let parsed = parseRule r; in "Bash(${parsed.pattern})")
|
||||
(lib.filter (r: (parseRule r).action == "deny") (permissions.bash.rules or []));
|
||||
editRules =
|
||||
if !(permissions ? edit)
|
||||
then []
|
||||
else
|
||||
map
|
||||
(r: let parsed = parseRule r; in "Edit(${parsed.pattern})")
|
||||
(lib.filter (r: (parseRule r).action == "deny") (permissions.edit.rules or []));
|
||||
in
|
||||
bashRules ++ editRules;
|
||||
|
||||
# Build YAML frontmatter for one Claude Code agent .md file.
|
||||
mkClaudeFrontmatter = name: agent: let
|
||||
descLine = "description: \"${agent.description}\"";
|
||||
modelLine =
|
||||
lib.optionalString
|
||||
(modelOverrides ? ${name})
|
||||
"model: ${modelOverrides.${name}}\n";
|
||||
skillsLine =
|
||||
if (agent ? skills) && agent.skills != []
|
||||
then "skills:\n" + lib.concatStringsSep "\n" (map (s: " - ${s}") agent.skills) + "\n"
|
||||
else "";
|
||||
in "---\n${descLine}\n${modelLine}${skillsLine}---\n";
|
||||
|
||||
mkClaudeAgentContent = name: agent:
|
||||
(mkClaudeFrontmatter name agent) + agent.systemPrompt;
|
||||
|
||||
mkClaudeAgentFile = name: agent:
|
||||
pkgs.writeText "${name}.md" (mkClaudeAgentContent name agent);
|
||||
|
||||
agentFiles = lib.mapAttrs mkClaudeAgentFile canonical;
|
||||
|
||||
# Build settings.json with permission rules aggregated from all agents.
|
||||
allAllows = lib.flatten (lib.mapAttrsToList (_: agent: renderPermAllow (agent.permissions or {})) canonical);
|
||||
allDenies = lib.flatten (lib.mapAttrsToList (_: agent: renderPermDeny (agent.permissions or {})) canonical);
|
||||
|
||||
settingsJson = builtins.toJSON {
|
||||
permissions = {
|
||||
allow = lib.unique (lib.sort (a: b: a < b) allAllows);
|
||||
deny = lib.unique (lib.sort (a: b: a < b) allDenies);
|
||||
};
|
||||
};
|
||||
|
||||
settingsFile = pkgs.writeText "claude-settings.json" settingsJson;
|
||||
|
||||
copyAgentCommands = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: file: "cp ${file} $out/.claude/agents/${name}.md") agentFiles
|
||||
);
|
||||
in
|
||||
pkgs.runCommand "claude-code-agents" {} ''
|
||||
mkdir -p $out/.claude/agents
|
||||
${copyAgentCommands}
|
||||
cp ${settingsFile} $out/.claude/settings.json
|
||||
'';
|
||||
|
||||
# ── Pi renderer ───────────────────────────────────────────────
|
||||
#
|
||||
# This renderer produces:
|
||||
# AGENTS.md — concatenated agent descriptions + specialist listing
|
||||
# SYSTEM.md — primary agent's system prompt (replaces Pi default)
|
||||
# agents/{name}.md — one per agent for pi-subagents (YAML frontmatter + prompt)
|
||||
#
|
||||
# The agents/ files use pi-subagents frontmatter format:
|
||||
# name, description, tools, extensions, model, thinking, skill,
|
||||
# output, defaultReads, defaultProgress, interactive, maxSubagentDepth
|
||||
|
||||
renderForPi = {
|
||||
pkgs,
|
||||
canonical,
|
||||
modelOverrides ? {},
|
||||
primaryAgent ? null,
|
||||
}: let
|
||||
# Find the primary agent (there should be exactly one).
|
||||
primaryAgents = lib.filterAttrs (_: a: a.mode == "primary") canonical;
|
||||
primaryNames = lib.attrNames primaryAgents;
|
||||
primaryName =
|
||||
if primaryAgent != null
|
||||
then primaryAgent
|
||||
else if primaryNames == []
|
||||
then throw "lib.agents.renderForPi: no primary agent found"
|
||||
else builtins.head primaryNames;
|
||||
primary = builtins.getAttr primaryName primaryAgents;
|
||||
|
||||
# Subagents for the specialist listing.
|
||||
subagents = lib.filterAttrs (_: a: a.mode != "primary") canonical;
|
||||
|
||||
# ── Permission → Pi tool mapping ──────────────────────────────
|
||||
#
|
||||
# Pi built-in tools: read, bash, edit, write, grep, find, ls,
|
||||
# mcp, subagent, web_search, fetch_content, etc.
|
||||
# Canonical tools: bash, edit, webfetch, websearch, question, external_directory
|
||||
#
|
||||
# We map canonical permissions to Pi's tool list.
|
||||
# intent=allow → include tool; intent=deny → exclude; intent=ask → include (Pi has no ask granularity)
|
||||
# When specific allow rules exist, the tool is always included (Pi can't restrict by pattern).
|
||||
|
||||
piToolsForAgent = agent: let
|
||||
perms = agent.permissions or {};
|
||||
tools = [];
|
||||
# Always available: read (no permission concept in Pi)
|
||||
addIf = tool: section:
|
||||
if section.intent == "allow" || section.intent == "ask"
|
||||
then [tool]
|
||||
else [];
|
||||
# bash → bash
|
||||
withBash = tools ++ (addIf "bash" (perms.bash or {intent = "ask";}));
|
||||
# edit → edit
|
||||
withEdit = withBash ++ (addIf "edit" (perms.edit or {intent = "deny";}));
|
||||
# webfetch → fetch_content
|
||||
withFetch = withEdit ++ (addIf "fetch_content" (perms.webfetch or {intent = "deny";}));
|
||||
# websearch → web_search
|
||||
withSearch = withFetch ++ (addIf "web_search" (perms.websearch or {intent = "deny";}));
|
||||
in
|
||||
lib.unique (withSearch ++ ["read" "grep" "find" "ls"]);
|
||||
|
||||
# ── Build YAML frontmatter for pi-subagents .md files ──────────
|
||||
mkPiFrontmatter = name: agent: let
|
||||
tools = piToolsForAgent agent;
|
||||
descLine = "description: \"${agent.description}\"";
|
||||
toolsLine = "tools: ${lib.concatStringsSep ", " tools}";
|
||||
model =
|
||||
if modelOverrides ? ${name}
|
||||
then "model: ${modelOverrides.${name}}"
|
||||
else "";
|
||||
skillsLine =
|
||||
if (agent ? skills) && agent.skills != []
|
||||
then "skill: ${lib.concatStringsSep ", " agent.skills}"
|
||||
else "";
|
||||
in
|
||||
"---\n"
|
||||
+ "name: ${name}\n"
|
||||
+ "${descLine}\n"
|
||||
+ "${toolsLine}\n"
|
||||
+ (lib.optionalString (model != "") "${model}\n")
|
||||
+ (lib.optionalString (skillsLine != "") "${skillsLine}\n")
|
||||
+ "---\n";
|
||||
|
||||
mkPiAgentContent = name: agent:
|
||||
(mkPiFrontmatter name agent) + agent.systemPrompt;
|
||||
|
||||
mkPiAgentFile = name: agent:
|
||||
pkgs.writeText "${name}.md" (mkPiAgentContent name agent);
|
||||
|
||||
piAgentFiles = lib.mapAttrs mkPiAgentFile canonical;
|
||||
|
||||
# ── Build AGENTS.md content ───────────────────────────────────
|
||||
primaryDn = primary.display_name or primaryName;
|
||||
specialistEntries = let
|
||||
mkEntry = name: agent: let
|
||||
dn = agent.display_name or name;
|
||||
in
|
||||
"- **" + dn + "**: " + agent.description;
|
||||
in
|
||||
lib.mapAttrsToList mkEntry subagents;
|
||||
agentsMd =
|
||||
"# Agent Instructions\n"
|
||||
+ "\n"
|
||||
+ "## "
|
||||
+ primaryDn
|
||||
+ "\n"
|
||||
+ "\n"
|
||||
+ primary.description
|
||||
+ "\n"
|
||||
+ "\n"
|
||||
+ (
|
||||
if subagents == {}
|
||||
then ""
|
||||
else "## Available Specialists\n\n" + lib.concatStringsSep "\n" specialistEntries + "\n"
|
||||
);
|
||||
|
||||
agentsMdFile = pkgs.writeText "AGENTS.md" agentsMd;
|
||||
systemMdFile = pkgs.writeText "SYSTEM.md" primary.systemPrompt;
|
||||
|
||||
copyAgentCommands = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: file: "cp ${file} $out/agents/${name}.md") piAgentFiles
|
||||
);
|
||||
in
|
||||
pkgs.runCommand "pi-agents" {} ''
|
||||
mkdir -p $out/agents
|
||||
cp ${agentsMdFile} $out/AGENTS.md
|
||||
cp ${systemMdFile} $out/SYSTEM.md
|
||||
${copyAgentCommands}
|
||||
'';
|
||||
|
||||
# ── renderForTool dispatcher ──────────────────────────────────
|
||||
#
|
||||
# Dispatches to the correct renderer by tool name.
|
||||
# tool: "opencode" | "claude-code" | "pi"
|
||||
|
||||
renderForTool = {
|
||||
pkgs,
|
||||
agentsInput,
|
||||
tool,
|
||||
modelOverrides ? {},
|
||||
}: let
|
||||
canonical = agentsInput.lib.loadAgents;
|
||||
in
|
||||
if tool == "opencode"
|
||||
then
|
||||
agentsLib.renderForOpencode {
|
||||
inherit pkgs canonical modelOverrides;
|
||||
}
|
||||
else if tool == "claude-code"
|
||||
then
|
||||
agentsLib.renderForClaudeCode {
|
||||
inherit pkgs canonical modelOverrides;
|
||||
}
|
||||
else if tool == "pi"
|
||||
then
|
||||
agentsLib.renderForPi {
|
||||
inherit pkgs canonical modelOverrides;
|
||||
}
|
||||
else throw "lib.agents.renderForTool: unknown tool '${tool}'. Must be opencode, claude-code, or pi.";
|
||||
|
||||
# ── shellHookForTool ──────────────────────────────────────────
|
||||
#
|
||||
# Generates a shellHook string for use in devShells that symlinks
|
||||
# rendered agent files into the project directory.
|
||||
#
|
||||
# Usage:
|
||||
# devShells.default = pkgs.mkShell {
|
||||
# shellHook = m3taLib.agents.shellHookForTool {
|
||||
# inherit pkgs;
|
||||
# agentsInput = inputs.agents;
|
||||
# tool = "opencode";
|
||||
# modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||
# };
|
||||
# };
|
||||
|
||||
shellHookForTool = {
|
||||
pkgs,
|
||||
agentsInput,
|
||||
tool,
|
||||
modelOverrides ? {},
|
||||
}: let
|
||||
rendered = agentsLib.renderForTool {
|
||||
inherit pkgs agentsInput tool modelOverrides;
|
||||
};
|
||||
in
|
||||
if tool == "opencode"
|
||||
then ''
|
||||
# Agent files for OpenCode
|
||||
mkdir -p .opencode/agents
|
||||
ln -sfn ${rendered}/* .opencode/agents/
|
||||
''
|
||||
else if tool == "claude-code"
|
||||
then ''
|
||||
# Agent files for Claude Code
|
||||
mkdir -p .claude/agents
|
||||
ln -sfn ${rendered}/.claude/agents/* .claude/agents/
|
||||
ln -sfn ${rendered}/.claude/settings.json .claude/settings.json
|
||||
''
|
||||
else if tool == "pi"
|
||||
then ''
|
||||
# Agent files for Pi
|
||||
ln -sfn ${rendered}/AGENTS.md AGENTS.md
|
||||
mkdir -p .pi
|
||||
ln -sfn ${rendered}/SYSTEM.md .pi/SYSTEM.md
|
||||
mkdir -p .pi/agents
|
||||
ln -sfn ${rendered}/agents/* .pi/agents/
|
||||
''
|
||||
else throw "lib.agents.shellHookForTool: unknown tool '${tool}'";
|
||||
};
|
||||
in
|
||||
agentsLib
|
||||
@@ -0,0 +1,120 @@
|
||||
# Opencode rules management utilities
|
||||
#
|
||||
# This module provides functions to configure Opencode agent rules across
|
||||
# multiple projects. Rules are defined in the AGENTS repository and can be
|
||||
# selectively included based on language, framework, and concerns.
|
||||
#
|
||||
# Usage in your configuration:
|
||||
#
|
||||
# # In your flake or configuration:
|
||||
# let
|
||||
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||
#
|
||||
# rules = m3taLib.coding-rules.mkCodingRules {
|
||||
# agents = inputs.agents;
|
||||
# languages = [ "python" "typescript" ];
|
||||
# concerns = [ "coding-style" "naming" "documentation" ];
|
||||
# frameworks = [ "react" "fastapi" ];
|
||||
# };
|
||||
# in {
|
||||
# # Use in your devShell:
|
||||
# devShells.default = pkgs.mkShell {
|
||||
# shellHook = rules.shellHook;
|
||||
# inherit (rules) instructions;
|
||||
# };
|
||||
# }
|
||||
#
|
||||
# The shellHook creates:
|
||||
# - A `.opencode-rules/` symlink pointing to the AGENTS repository rules directory
|
||||
# - A `coding-rules.json` file with a $schema reference and instructions list
|
||||
#
|
||||
# The instructions list contains paths relative to the project root, all prefixed
|
||||
# with `.opencode-rules/`, making them portable across different project locations.
|
||||
{lib}: let
|
||||
# Create Opencode rules configuration from AGENTS repository
|
||||
#
|
||||
# Args:
|
||||
# agents: Path to the AGENTS repository (non-flake input)
|
||||
# languages: Optional list of language-specific rules to include
|
||||
# (e.g., [ "python" "typescript" "rust" ])
|
||||
# concerns: Optional list of concern rules to include
|
||||
# Default: [ "coding-style" "naming" "documentation" "testing" "git-workflow" "project-structure" ]
|
||||
# frameworks: Optional list of framework-specific rules to include
|
||||
# (e.g., [ "react" "fastapi" "django" ])
|
||||
# extraInstructions: Optional list of additional instruction paths
|
||||
# (for custom rules outside standard locations)
|
||||
#
|
||||
# Returns:
|
||||
# An attribute set containing:
|
||||
# - shellHook: Bash code to create symlink and coding-rules.json
|
||||
# - instructions: List of rule file paths (relative to project root)
|
||||
#
|
||||
# Example:
|
||||
# mkCodingRules {
|
||||
# agents = inputs.agents;
|
||||
# languages = [ "python" ];
|
||||
# frameworks = [ "fastapi" ];
|
||||
# }
|
||||
# # Returns:
|
||||
# # {
|
||||
# # shellHook = "...";
|
||||
# # instructions = [
|
||||
# # ".opencode-rules/concerns/coding-style.md"
|
||||
# # ".opencode-rules/concerns/naming.md"
|
||||
# # ".opencode-rules/concerns/documentation.md"
|
||||
# # ".opencode-rules/concerns/testing.md"
|
||||
# # ".opencode-rules/concerns/git-workflow.md"
|
||||
# # ".opencode-rules/concerns/project-structure.md"
|
||||
# # ".opencode-rules/languages/python.md"
|
||||
# # ".opencode-rules/frameworks/fastapi.md"
|
||||
# # ];
|
||||
# # }
|
||||
mkCodingRules = {
|
||||
agents,
|
||||
languages ? [],
|
||||
concerns ? [
|
||||
"coding-style"
|
||||
"naming"
|
||||
"documentation"
|
||||
"testing"
|
||||
"git-workflow"
|
||||
"project-structure"
|
||||
],
|
||||
frameworks ? [],
|
||||
extraInstructions ? [],
|
||||
rulesDir ? ".opencode-rules",
|
||||
}: let
|
||||
# Build instructions list by mapping concerns, languages, frameworks to their file paths
|
||||
# All paths are relative to project root via the rulesDir symlink
|
||||
instructions =
|
||||
(map (c: "${rulesDir}/concerns/${c}.md") concerns)
|
||||
++ (map (l: "${rulesDir}/languages/${l}.md") languages)
|
||||
++ (map (f: "${rulesDir}/frameworks/${f}.md") frameworks)
|
||||
++ extraInstructions;
|
||||
|
||||
# Generate JSON configuration for coding rules
|
||||
rulesConfig = {
|
||||
"$schema" = "https://opencode.ai/config.json";
|
||||
inherit instructions;
|
||||
};
|
||||
in {
|
||||
inherit instructions;
|
||||
|
||||
# Shell hook to set up rules in the project
|
||||
# Creates a symlink to the AGENTS rules directory and generates coding-rules.json
|
||||
shellHook = ''
|
||||
# Create/update symlink to AGENTS rules directory
|
||||
ln -sfn ${agents}/rules ${rulesDir}
|
||||
|
||||
# Generate coding-rules.json configuration file
|
||||
cat > coding-rules.json <<'RULES_EOF'
|
||||
${builtins.toJSON rulesConfig}
|
||||
RULES_EOF
|
||||
'';
|
||||
};
|
||||
|
||||
# Backward-compat alias
|
||||
mkOpencodeRules = mkCodingRules;
|
||||
in {
|
||||
inherit mkCodingRules mkOpencodeRules;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Library of helper functions for m3ta-nixpkgs
|
||||
# Usage in your configuration:
|
||||
# let
|
||||
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||
# in ...
|
||||
{lib}: {
|
||||
# Port management utilities
|
||||
ports = import ./ports.nix {inherit lib;};
|
||||
|
||||
# Coding rules injection utilities (renamed from opencode-rules)
|
||||
coding-rules = import ./coding-rules.nix {inherit lib;};
|
||||
|
||||
# Backward-compat alias: opencode-rules → coding-rules
|
||||
opencode-rules = import ./coding-rules.nix {inherit lib;};
|
||||
|
||||
# Agent configuration management utilities
|
||||
agents = import ./agents.nix {inherit lib;};
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
# Port management utilities
|
||||
#
|
||||
# This module provides functions to manage service ports across multiple hosts
|
||||
# in a centralized way. Ports are defined in your configuration and can have
|
||||
# host-specific overrides.
|
||||
#
|
||||
# Usage in your configuration:
|
||||
#
|
||||
# # In your flake or configuration, define your ports:
|
||||
# let
|
||||
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||
#
|
||||
# myPorts = {
|
||||
# ports = {
|
||||
# nginx = 80;
|
||||
# grafana = 3000;
|
||||
# prometheus = 9090;
|
||||
# homepage = 8080;
|
||||
# };
|
||||
# hostPorts = {
|
||||
# laptop = {
|
||||
# nginx = 8080; # Override nginx port on laptop
|
||||
# };
|
||||
# server = {
|
||||
# homepage = 3001; # Override homepage port on server
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# portHelpers = m3taLib.ports.mkPortHelpers myPorts;
|
||||
# in {
|
||||
# # Use in your config:
|
||||
# services.nginx.port = portHelpers.getPort "nginx" "laptop";
|
||||
# # Returns: 8080 (host-specific override)
|
||||
#
|
||||
# services.grafana.port = portHelpers.getPort "grafana" "laptop";
|
||||
# # Returns: 3000 (default port)
|
||||
#
|
||||
# # Get all ports for a specific host (defaults + overrides):
|
||||
# allLaptopPorts = portHelpers.getHostPorts "laptop";
|
||||
# # Returns: { nginx = 8080; grafana = 3000; prometheus = 9090; homepage = 8080; }
|
||||
# }
|
||||
{lib}: {
|
||||
# Create port helper functions from a ports configuration
|
||||
#
|
||||
# Args:
|
||||
# portsConfig: An attribute set with structure:
|
||||
# {
|
||||
# ports = { service-name = port-number; ... };
|
||||
# hostPorts = { hostname = { service-name = port-number; ... }; ... };
|
||||
# }
|
||||
#
|
||||
# Returns:
|
||||
# An attribute set containing helper functions:
|
||||
# - getPort: Get port for a service with optional host override
|
||||
# - getHostPorts: Get all ports for a specific host
|
||||
# - listServices: List all defined services
|
||||
mkPortHelpers = portsConfig: let
|
||||
ports = portsConfig.ports or {};
|
||||
hostPorts = portsConfig.hostPorts or {};
|
||||
in {
|
||||
# Get port for a service, with optional host-specific override
|
||||
#
|
||||
# Args:
|
||||
# service: The service name (string)
|
||||
# host: The hostname (string)
|
||||
#
|
||||
# Returns:
|
||||
# Port number (int) or null if service not found
|
||||
#
|
||||
# Example:
|
||||
# getPort "nginx" "laptop" # Returns host-specific port if defined
|
||||
# getPort "nginx" null # Returns default port
|
||||
getPort = service: host:
|
||||
if host != null && hostPorts ? ${host} && hostPorts.${host} ? ${service}
|
||||
then hostPorts.${host}.${service}
|
||||
else ports.${service} or null;
|
||||
|
||||
# Get all ports for a specific host (merges defaults with host overrides)
|
||||
#
|
||||
# Args:
|
||||
# host: The hostname (string)
|
||||
#
|
||||
# Returns:
|
||||
# Attribute set of all ports for the host
|
||||
#
|
||||
# Example:
|
||||
# getHostPorts "laptop" # { nginx = 8080; grafana = 3000; ... }
|
||||
getHostPorts = host:
|
||||
ports // (hostPorts.${host} or {});
|
||||
|
||||
# List all defined service names
|
||||
#
|
||||
# Returns:
|
||||
# List of service names (strings)
|
||||
listServices = lib.attrNames ports;
|
||||
};
|
||||
|
||||
# Simple helper to get a port without host override
|
||||
# Useful when you don't need host-specific ports
|
||||
#
|
||||
# Args:
|
||||
# portsConfig: Same structure as mkPortHelpers
|
||||
# service: The service name (string)
|
||||
#
|
||||
# Returns:
|
||||
# Port number (int) or null if service not found
|
||||
#
|
||||
# Example:
|
||||
# getDefaultPort myPorts "nginx" # Returns default port only
|
||||
getDefaultPort = portsConfig: service:
|
||||
portsConfig.ports.${service} or null;
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
# Home Manager Modules
|
||||
|
||||
User-level configuration modules organized by functional category.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
home-manager/
|
||||
├── default.nix # Aggregator: imports all categories + ports.nix
|
||||
├── ports.nix # Port management (HM-specific: generateEnvVars)
|
||||
├── cli/ # Terminal/CLI tools
|
||||
│ ├── default.nix # Category aggregator
|
||||
│ └── zellij-ps.nix
|
||||
└── coding/ # Development tools
|
||||
├── default.nix # Category aggregator
|
||||
├── editors.nix # Neovim + Zed configs
|
||||
├── opencode.nix # OpenCode non-agent config (theme, plugins, formatter)
|
||||
└── agents/ # Per-tool agent deployment (canonical TOML → rendered)
|
||||
├── default.nix
|
||||
├── opencode.nix # File-based agents + skills + context
|
||||
├── claude-code.nix # Claude Code agents + settings.json
|
||||
└── pi.nix # Pi AGENTS.md + SYSTEM.md
|
||||
```
|
||||
|
||||
## Where to Look
|
||||
|
||||
| Task | Location |
|
||||
|------|----------|
|
||||
| Add CLI module | `cli/<name>.nix`, import in `cli/default.nix` |
|
||||
| Add coding module | `coding/<name>.nix`, import in `coding/default.nix` |
|
||||
| Add new category | Create `<category>/default.nix`, import in root `default.nix` |
|
||||
| Module with host ports | Import `../../lib/ports.nix`, use `mkPortHelpers` |
|
||||
| Add agent renderer | `coding/agents/<tool>.nix`, import in `coding/agents/default.nix` |
|
||||
|
||||
## Option Namespaces
|
||||
|
||||
- `cli.*` - CLI tools (e.g., `cli.zellij-ps.enable`)
|
||||
- `coding.editors.*` - Editor configs (e.g., `coding.editors.neovim.enable`)
|
||||
- `coding.opencode.*` - OpenCode non-agent config (theme, plugins, formatter)
|
||||
- `coding.agents.opencode.*` - OpenCode agent deployment (file-based agents)
|
||||
- `coding.agents.claude-code.*` - Claude Code agent deployment
|
||||
- `coding.agents.pi.*` - Pi agent deployment
|
||||
- `m3ta.ports.*` - Port management (shared with NixOS)
|
||||
|
||||
## Patterns
|
||||
|
||||
**Category aggregator** (`cli/default.nix`):
|
||||
```nix
|
||||
{
|
||||
imports = [
|
||||
./zellij-ps.nix
|
||||
# Add new modules here
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**Simple module** (zellij-ps):
|
||||
```nix
|
||||
options.cli.zellij-ps = {
|
||||
enable = mkEnableOption "...";
|
||||
projectFolders = mkOption { type = types.listOf types.path; ... };
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
home.packages = [ pkgs.zellij-ps ];
|
||||
home.sessionVariables.PROJECT_FOLDERS = ...;
|
||||
};
|
||||
```
|
||||
|
||||
**Multi-config module** (editors.nix):
|
||||
```nix
|
||||
config = mkMerge [
|
||||
(mkIf cfg.neovim.enable { programs.neovim = {...}; })
|
||||
(mkIf cfg.zed.enable { programs.zed-editor = {...}; })
|
||||
(mkIf (cfg.neovim.enable || cfg.zed.enable) { home.packages = [...]; })
|
||||
];
|
||||
```
|
||||
|
||||
## HM vs NixOS Differences
|
||||
|
||||
| Feature | Home Manager | NixOS |
|
||||
|---------|--------------|-------|
|
||||
| `currentHost` default | `null` (must set) | `config.networking.hostName` |
|
||||
| `generateEnvVars` | Available | Not available |
|
||||
| Output file | `~/.config/m3ta/ports.json` | `/etc/m3ta/ports.json` |
|
||||
| Package access | `pkgs.*` via overlay | `pkgs.*` via overlay |
|
||||
|
||||
## Agent Modules
|
||||
|
||||
Agent definitions are stored as canonical `agent.toml` + `system-prompt.md` in the
|
||||
[AGENTS repo](https://code.m3ta.dev/m3tam3re/AGENTS). Renderers in `lib/agents.nix`
|
||||
transform these into tool-specific configs. Each tool has its own HM sub-module
|
||||
under `coding/agents/`.
|
||||
|
||||
### OpenCode (`coding.agents.opencode`)
|
||||
|
||||
Renders file-based agents to `~/.config/opencode/agents/*.md`:
|
||||
|
||||
```nix
|
||||
coding.agents.opencode = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
modelOverrides = {
|
||||
chiron = "anthropic/claude-sonnet-4";
|
||||
};
|
||||
externalSkills = [
|
||||
{ src = inputs.skills-anthropic; }
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
**Options:** `enable`, `agentsInput`, `modelOverrides`, `externalSkills`
|
||||
|
||||
### Claude Code (`coding.agents.claude-code`)
|
||||
|
||||
Renders agents to `~/.claude/agents/*.md` + `~/.claude/settings.json`:
|
||||
|
||||
```nix
|
||||
coding.agents.claude-code = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
modelOverrides = {};
|
||||
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||
};
|
||||
```
|
||||
|
||||
**Options:** `enable`, `agentsInput`, `modelOverrides`, `externalSkills`
|
||||
|
||||
### Pi (`coding.agents.pi`)
|
||||
|
||||
Renders `AGENTS.md` + `SYSTEM.md` to `~/.pi/agent/` by default:
|
||||
|
||||
```nix
|
||||
coding.agents.pi = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
path = ".pi/agent"; # default, relative to $HOME
|
||||
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||
};
|
||||
```
|
||||
|
||||
**Options:** `enable`, `path`, `agentsInput`, `modelOverrides`, `externalSkills`, `primaryAgent`, `mcpServers`, `settings`
|
||||
|
||||
### Project-level usage
|
||||
|
||||
For per-project agent setup via `flake.nix` + `direnv`:
|
||||
|
||||
```nix
|
||||
m3taLib.agents.shellHookForTool {
|
||||
inherit pkgs;
|
||||
agentsInput = inputs.agents;
|
||||
tool = "opencode";
|
||||
modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||
};
|
||||
```
|
||||
|
||||
## Migration Guide (OpenCode agents)
|
||||
|
||||
The agent system was migrated from embedded `agents.json` to file-based canonical
|
||||
`agent.toml` definitions. Here is how to migrate your home-manager config.
|
||||
|
||||
### What changed
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| `coding.opencode.agentsInput` | `coding.agents.opencode.agentsInput` |
|
||||
| `coding.opencode.externalSkills` | `coding.agents.opencode.externalSkills` |
|
||||
| Agents embedded in `config.json` | File-based `~/.config/opencode/agents/*.md` |
|
||||
| Model hardcoded in `agents.json` | Per-machine `modelOverrides` |
|
||||
| `mkOpencodeRules` | `mkCodingRules` (old name still works) |
|
||||
|
||||
### Migration steps
|
||||
|
||||
**1. Update home-manager config:**
|
||||
|
||||
Move `agentsInput` and `externalSkills` from `coding.opencode` to `coding.agents.opencode`.
|
||||
Add `modelOverrides` with the models previously hardcoded in agents.json:
|
||||
|
||||
```nix
|
||||
# BEFORE (legacy):
|
||||
coding.opencode = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||
ohMyOpencodeSettings = { ... };
|
||||
};
|
||||
|
||||
# AFTER (new):
|
||||
coding.opencode = {
|
||||
enable = true;
|
||||
ohMyOpencodeSettings = { ... };
|
||||
};
|
||||
|
||||
coding.agents.opencode = {
|
||||
enable = true;
|
||||
agentsInput = inputs.agents;
|
||||
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||
modelOverrides = {
|
||||
chiron = "zai-coding-plan/glm-5";
|
||||
"chiron-forge" = "zai-coding-plan/glm-5";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**2. Run `home-manager switch`:**
|
||||
|
||||
```bash
|
||||
home-manager switch --flake .
|
||||
```
|
||||
|
||||
**3. Verify agents are deployed:**
|
||||
|
||||
```bash
|
||||
ls ~/.config/opencode/agents/
|
||||
# Should show: chiron.md chiron-forge.md hermes.md athena.md apollo.md calliope.md
|
||||
```
|
||||
|
||||
**4. Remove legacy files from AGENTS repo** (after confirming everything works):
|
||||
|
||||
```bash
|
||||
cd /home/m3tam3re/p/AI/AGENTS
|
||||
rm agents/agents.json
|
||||
rm prompts/chiron.txt prompts/chiron-forge.txt prompts/hermes.txt \
|
||||
prompts/athena.txt prompts/apollo.txt prompts/calliope.txt
|
||||
rmdir prompts/ # if empty
|
||||
# Also remove lib.agentsJson from flake.nix
|
||||
```
|
||||
|
||||
**5. Final cleanup:** After legacy files are removed from AGENTS repo,
|
||||
remove `lib.agentsJson` from the AGENTS `flake.nix` (it's only needed for
|
||||
backward compatibility during the transition).
|
||||
|
||||
### Key advantage of the new system
|
||||
|
||||
Prompt changes no longer require `home-manager switch`. Since agents are
|
||||
deployed as file-based `~/.config/opencode/agents/*.md` (symlinks to Nix store),
|
||||
you only need to edit the `system-prompt.md` in the AGENTS repo, commit, update
|
||||
the flake lock, and run `home-manager switch`. Or for local development, edit
|
||||
the file directly and restart the tool.
|
||||
@@ -0,0 +1,8 @@
|
||||
# CLI/Terminal-related Home Manager modules
|
||||
{
|
||||
imports = [
|
||||
./rofi-project-opener.nix
|
||||
./stt-ptt.nix
|
||||
./zellij-ps.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.cli.rofi-project-opener;
|
||||
|
||||
# Project directory submodule type
|
||||
projectDirType = types.submodule {
|
||||
options = {
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
description = "Base directory path to scan for project subdirectories.";
|
||||
example = "~/dev";
|
||||
};
|
||||
args = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Additional arguments to pass to opencode when launching projects from this directory.";
|
||||
example = "--agent Planner-Sisyphus";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Convert projectDirs attrset to JSON for config file
|
||||
projectDirsJson = builtins.toJSON (
|
||||
mapAttrs (name: value: {
|
||||
path = value.path;
|
||||
args = value.args;
|
||||
})
|
||||
cfg.projectDirs
|
||||
);
|
||||
in {
|
||||
options.cli.rofi-project-opener = {
|
||||
enable = mkEnableOption "Rofi-based project directory launcher";
|
||||
|
||||
projectDirs = mkOption {
|
||||
type = types.attrsOf projectDirType;
|
||||
default = {
|
||||
dev = {path = "~/dev";};
|
||||
projects = {path = "~/projects";};
|
||||
};
|
||||
description = ''
|
||||
Attribute set of base directories to scan for project subdirectories.
|
||||
Each directory will be scanned for immediate subdirectories (non-hidden).
|
||||
Projects are displayed as "base_dir/project_name" in rofi.
|
||||
|
||||
Each entry can specify:
|
||||
- path: Base directory path (supports ~ for home directory)
|
||||
- args: Optional arguments to pass to opencode for projects in this directory
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
nixpkgs = { path = "~/p/NIX/nixpkgs"; args = "--agent Planner-Sisyphus"; };
|
||||
dev = { path = "~/dev"; };
|
||||
work = { path = "~/work"; args = "--agent work-agent"; };
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
terminal = mkOption {
|
||||
type = types.either types.str types.package;
|
||||
default = "kitty";
|
||||
description = "Terminal emulator to use for launching opencode. Can be a string or package.";
|
||||
example = literalExpression "pkgs.alacritty";
|
||||
};
|
||||
|
||||
terminalCommand = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Custom command to run in the terminal.
|
||||
|
||||
Placeholders:
|
||||
- %s = project path
|
||||
- %a = project args (from projectDirs.<name>.args)
|
||||
|
||||
If empty, defaults to: cd to project, run "opencode %a"
|
||||
|
||||
Examples:
|
||||
- "" (empty) - Uses default: cd to project, run opencode with args
|
||||
- "opencode %a" - Run opencode with project-specific args
|
||||
- "nvim" - Open editor (no args)
|
||||
- "myapp %s %a" - Custom app with path and args
|
||||
'';
|
||||
example = literalExpression ''"opencode %a"'';
|
||||
};
|
||||
|
||||
rofiPrompt = mkOption {
|
||||
type = types.str;
|
||||
default = "Select project";
|
||||
description = "Prompt text displayed in rofi.";
|
||||
example = "Open project:";
|
||||
};
|
||||
|
||||
rofiArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = ["-dmenu" "-i"];
|
||||
description = ''
|
||||
Arguments to pass to rofi.
|
||||
|
||||
Common options:
|
||||
- "-dmenu" - Enable dmenu mode (required)
|
||||
- "-i" - Case-insensitive matching
|
||||
- "-theme <theme>" - Use specific rofi theme
|
||||
- "-width <percentage>" - Window width
|
||||
- "-lines <number>" - Number of visible lines
|
||||
'';
|
||||
example = literalExpression ''["-dmenu" "-i" "-theme gruvbox"]'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
home.packages = [pkgs.rofi-project-opener];
|
||||
|
||||
# Write JSON config file for project directories
|
||||
xdg.configFile."rofi-project-opener/projects.json".text = projectDirsJson;
|
||||
|
||||
# Write shell config file for other settings
|
||||
xdg.configFile."rofi-project-opener/config".text = ''
|
||||
# rofi-project-opener configuration
|
||||
TERMINAL="${
|
||||
if isDerivation cfg.terminal
|
||||
then "${cfg.terminal}/bin/${cfg.terminal.pname or (builtins.baseNameOf (toString cfg.terminal))}"
|
||||
else cfg.terminal
|
||||
}"
|
||||
${optionalString (cfg.terminalCommand != "") ''TERMINAL_CMD="${cfg.terminalCommand}"''}
|
||||
ROFI_PROMPT="${cfg.rofiPrompt}"
|
||||
ROFI_ARGS="${escapeShellArgs cfg.rofiArgs}"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.cli.stt-ptt;
|
||||
|
||||
# Build stt-ptt package with the selected whisper package
|
||||
sttPttPackage = pkgs.stt-ptt.override {
|
||||
whisper-cpp = cfg.whisperPackage;
|
||||
};
|
||||
|
||||
modelDir = "${config.xdg.dataHome}/stt-ptt/models";
|
||||
modelPath = "${modelDir}/${cfg.model}.bin";
|
||||
|
||||
# HuggingFace URL for whisper.cpp models
|
||||
modelUrl = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${cfg.model}.bin";
|
||||
in {
|
||||
options.cli.stt-ptt = {
|
||||
enable = mkEnableOption "Push to Talk Speech to Text using Whisper";
|
||||
|
||||
whisperPackage = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.whisper-cpp;
|
||||
description = ''
|
||||
The whisper-cpp package to use. Available options:
|
||||
|
||||
Pre-built variants:
|
||||
- `pkgs.whisper-cpp` - CPU-based inference (default)
|
||||
- `pkgs.whisper-cpp-vulkan` - Vulkan GPU acceleration
|
||||
|
||||
Override options (can be combined):
|
||||
- `cudaSupport` - NVIDIA CUDA support
|
||||
- `rocmSupport` - AMD ROCm support
|
||||
- `vulkanSupport` - Vulkan support
|
||||
- `coreMLSupport` - Apple CoreML (macOS only)
|
||||
- `metalSupport` - Apple Metal (macOS ARM only)
|
||||
|
||||
Example overrides:
|
||||
- `pkgs.whisper-cpp.override { cudaSupport = true; }` - NVIDIA GPU
|
||||
- `pkgs.whisper-cpp.override { rocmSupport = true; }` - AMD GPU
|
||||
- `pkgs.whisper-cpp.override { vulkanSupport = true; }` - Vulkan
|
||||
'';
|
||||
example = literalExpression "pkgs.whisper-cpp.override { cudaSupport = true; }";
|
||||
};
|
||||
|
||||
model = mkOption {
|
||||
type = types.str;
|
||||
default = "ggml-large-v3-turbo";
|
||||
description = ''
|
||||
The Whisper model to use. Models are downloaded from HuggingFace.
|
||||
|
||||
Available models (sorted by size/quality):
|
||||
- `ggml-tiny` / `ggml-tiny.en` - 75MB, fastest, lowest quality
|
||||
- `ggml-base` / `ggml-base.en` - 142MB, fast, basic quality
|
||||
- `ggml-small` / `ggml-small.en` - 466MB, balanced
|
||||
- `ggml-medium` / `ggml-medium.en` - 1.5GB, good quality
|
||||
- `ggml-large-v1` - 2.9GB, high quality (original)
|
||||
- `ggml-large-v2` - 2.9GB, high quality (improved)
|
||||
- `ggml-large-v3` - 2.9GB, highest quality
|
||||
- `ggml-large-v3-turbo` - 1.6GB, high quality, optimized speed (recommended)
|
||||
|
||||
Models ending in `.en` are English-only and slightly faster for English.
|
||||
Quantized versions (q5_0, q5_1, q8_0) are also available for reduced size.
|
||||
'';
|
||||
example = "ggml-base.en";
|
||||
};
|
||||
|
||||
notifyTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 3000;
|
||||
description = ''
|
||||
Notification timeout in milliseconds for the recording indicator.
|
||||
Set to 0 for persistent notifications.
|
||||
'';
|
||||
example = 5000;
|
||||
};
|
||||
|
||||
language = mkOption {
|
||||
type = types.enum [
|
||||
"auto"
|
||||
"en"
|
||||
"es"
|
||||
"fr"
|
||||
"de"
|
||||
"it"
|
||||
"pt"
|
||||
"ru"
|
||||
"zh"
|
||||
"ja"
|
||||
"ko"
|
||||
"ar"
|
||||
"hi"
|
||||
"tr"
|
||||
"pl"
|
||||
"nl"
|
||||
"sv"
|
||||
"da"
|
||||
"fi"
|
||||
"no"
|
||||
"vi"
|
||||
"th"
|
||||
"id"
|
||||
"uk"
|
||||
"cs"
|
||||
];
|
||||
default = "auto";
|
||||
description = ''
|
||||
Language for speech recognition. Use "auto" for automatic language detection,
|
||||
or specify a language code (ISO 639-1 standard) for better accuracy.
|
||||
|
||||
Auto-detection analyzes the audio to determine the spoken language automatically.
|
||||
Specifying a language can improve accuracy if you know the language in advance.
|
||||
|
||||
Common language codes:
|
||||
- en: English
|
||||
- es: Spanish
|
||||
- fr: French
|
||||
- de: German
|
||||
- zh: Chinese
|
||||
- ja: Japanese
|
||||
- ko: Korean
|
||||
|
||||
whisper.cpp supports 100+ languages. See whisper.cpp documentation for the full list.
|
||||
'';
|
||||
example = "en";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
home.packages = [sttPttPackage];
|
||||
|
||||
home.sessionVariables = {
|
||||
STT_MODEL = modelPath;
|
||||
STT_LANGUAGE = cfg.language;
|
||||
STT_NOTIFY_TIMEOUT = toString cfg.notifyTimeout;
|
||||
};
|
||||
|
||||
# Create model directory and download model if not present
|
||||
home.activation.downloadWhisperModel = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||
MODEL_DIR="${modelDir}"
|
||||
MODEL_PATH="${modelPath}"
|
||||
MODEL_URL="${modelUrl}"
|
||||
|
||||
$DRY_RUN_CMD mkdir -p "$MODEL_DIR"
|
||||
|
||||
if [ ! -f "$MODEL_PATH" ]; then
|
||||
echo "Downloading Whisper model: ${cfg.model}..."
|
||||
$DRY_RUN_CMD ${pkgs.curl}/bin/curl -L -o "$MODEL_PATH" "$MODEL_URL" || {
|
||||
echo "Failed to download model from $MODEL_URL"
|
||||
echo "Please download manually and place at: $MODEL_PATH"
|
||||
}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.cli.zellij-ps;
|
||||
in {
|
||||
options.cli.zellij-ps = {
|
||||
enable = mkEnableOption "Zellij Project Selector";
|
||||
|
||||
projectFolders = mkOption {
|
||||
type = types.listOf types.path;
|
||||
description = "List of project folders for zellij-ps.";
|
||||
default = ["${config.home.homeDirectory}/projects"];
|
||||
};
|
||||
|
||||
layout = mkOption {
|
||||
type = types.str;
|
||||
description = "Layout for zellij";
|
||||
default = ''
|
||||
layout {
|
||||
pane size=1 borderless=true {
|
||||
plugin location="zellij:tab-bar"
|
||||
}
|
||||
pane
|
||||
pane split_direction="vertical" {
|
||||
pane
|
||||
pane command="htop"
|
||||
}
|
||||
pane size=2 borderless=true {
|
||||
plugin location="zellij:status-bar"
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
home.packages = [pkgs.zellij-ps];
|
||||
home.sessionVariables.PROJECT_FOLDERS = lib.concatStringsSep ":" cfg.projectFolders;
|
||||
home.file.".config/zellij/layouts/zellij-ps.kdl".text = cfg.layout;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.claude-code;
|
||||
mcpCfg = config.programs.mcp or null;
|
||||
in {
|
||||
options.coding.agents.claude-code = {
|
||||
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions";
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, agents are rendered from canonical agent.toml files
|
||||
and symlinked to ~/.claude/agents/.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides. Maps agent slug to model alias or ID.
|
||||
Example: { chiron = "claude-sonnet-4-20250514"; }
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
chiron = "claude-sonnet-4-20250514";
|
||||
"chiron-forge" = "claude-sonnet-4-20250514";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
externalSkills = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
src = mkOption {
|
||||
type = types.anything;
|
||||
description = "Flake input pointing to a skills repository root.";
|
||||
};
|
||||
skillsDir = mkOption {
|
||||
type = types.str;
|
||||
default = "skills";
|
||||
description = ''
|
||||
Subdirectory inside src that contains skill folders.
|
||||
'';
|
||||
};
|
||||
selectSkills = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
List of skill names to cherry-pick from this source.
|
||||
null means include every skill found in skillsDir.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
description = ''
|
||||
External skill sources passed to mkOpencodeSkills.
|
||||
Each entry maps directly to an element of the externalSkills
|
||||
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
||||
{ src = inputs.skills-vercel; }
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
mcpServers = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = if mcpCfg != null then mcpCfg.servers else {};
|
||||
defaultText = literalExpression "config.programs.mcp.servers";
|
||||
description = ''
|
||||
MCP server configurations for Claude Code.
|
||||
Merged into ~/.claude/settings.json alongside permissions.
|
||||
Automatically inherits from config.programs.mcp.servers.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (let
|
||||
agentsLib = (import ../../../../lib {inherit lib;}).agents;
|
||||
|
||||
# Rendered agents + permissions (only if agentsInput is set)
|
||||
rendered = mkIf (cfg.agentsInput != null) (
|
||||
agentsLib.renderForClaudeCode {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
}
|
||||
);
|
||||
|
||||
# Merge MCP servers into the rendered settings.json.
|
||||
# The renderer produces { permissions: { allow, deny } }.
|
||||
# We add mcpServers on top.
|
||||
settingsJson =
|
||||
if cfg.agentsInput != null
|
||||
then let
|
||||
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
|
||||
withMcp =
|
||||
if cfg.mcpServers != {}
|
||||
then renderedSettings // {mcpServers = cfg.mcpServers;}
|
||||
else renderedSettings;
|
||||
in
|
||||
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
|
||||
else if cfg.mcpServers != {}
|
||||
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
|
||||
else null;
|
||||
in {
|
||||
# Rendered agent files symlinked to ~/.claude/agents/
|
||||
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${rendered}/.claude/agents";
|
||||
};
|
||||
|
||||
# Skills (merged from personal AGENTS repo + optional external skills)
|
||||
home.file.".claude/skills" = mkIf (cfg.agentsInput != null) {
|
||||
source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||
inherit pkgs;
|
||||
customSkills = "${cfg.agentsInput}/skills";
|
||||
externalSkills =
|
||||
map (
|
||||
entry:
|
||||
{inherit (entry) src skillsDir;}
|
||||
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
||||
)
|
||||
cfg.externalSkills;
|
||||
};
|
||||
};
|
||||
|
||||
# Rendered settings.json with permissions + MCP servers
|
||||
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
|
||||
source = "${settingsJson}";
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Per-tool agent sub-modules
|
||||
# Each module handles rendering canonical agent.toml definitions
|
||||
# for a specific AI coding tool.
|
||||
{
|
||||
imports = [
|
||||
./opencode.nix
|
||||
./claude-code.nix
|
||||
./pi.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.opencode;
|
||||
in {
|
||||
options.coding.agents.opencode = {
|
||||
enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions";
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, agents are rendered from canonical agent.toml files
|
||||
and symlinked to ~/.config/opencode/agents/.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides. Maps agent slug to model string.
|
||||
Example: { chiron = "anthropic/claude-sonnet-4"; }
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
chiron = "anthropic/claude-sonnet-4";
|
||||
"chiron-forge" = "anthropic/claude-sonnet-4";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
externalSkills = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
src = mkOption {
|
||||
type = types.anything;
|
||||
description = "Flake input pointing to a skills repository root.";
|
||||
};
|
||||
skillsDir = mkOption {
|
||||
type = types.str;
|
||||
default = "skills";
|
||||
description = ''
|
||||
Subdirectory inside src that contains skill folders.
|
||||
'';
|
||||
};
|
||||
selectSkills = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
List of skill names to cherry-pick from this source.
|
||||
null means include every skill found in skillsDir.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
description = ''
|
||||
External skill sources passed to mkOpencodeSkills.
|
||||
Each entry maps directly to an element of the externalSkills
|
||||
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
||||
{ src = inputs.skills-vercel; }
|
||||
]
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Rendered agent files symlinked to ~/.config/opencode/agents/
|
||||
xdg.configFile."opencode/agents" = mkIf (cfg.agentsInput != null) {
|
||||
source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
};
|
||||
};
|
||||
|
||||
# Skills (merged from personal AGENTS repo + optional external skills)
|
||||
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) {
|
||||
source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||
inherit pkgs;
|
||||
customSkills = "${cfg.agentsInput}/skills";
|
||||
externalSkills =
|
||||
map (
|
||||
entry:
|
||||
{inherit (entry) src skillsDir;}
|
||||
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
||||
)
|
||||
cfg.externalSkills;
|
||||
};
|
||||
};
|
||||
|
||||
# Static config dirs from AGENTS repo
|
||||
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/context";
|
||||
};
|
||||
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/commands";
|
||||
};
|
||||
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/prompts";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.pi;
|
||||
mcpCfg = config.programs.mcp or null;
|
||||
in {
|
||||
options.coding.agents.pi = {
|
||||
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions";
|
||||
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
default = ".pi/agent";
|
||||
description = ''
|
||||
Relative path (inside the Home Manager user's home) where Pi agent
|
||||
config should be materialized.
|
||||
|
||||
Defaults to `.pi/agent`, i.e. `~/.pi/agent`.
|
||||
'';
|
||||
example = ".config/pi/agent";
|
||||
};
|
||||
|
||||
mcpServers = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default =
|
||||
if mcpCfg != null
|
||||
then mcpCfg.servers
|
||||
else {};
|
||||
defaultText = literalExpression "config.programs.mcp.servers";
|
||||
description = ''
|
||||
MCP server configurations for Pi (pi-mcp-adapter).
|
||||
Written to `${cfg.path}/mcp.json`.
|
||||
Automatically inherits from config.programs.mcp.servers.
|
||||
'';
|
||||
};
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, the primary agent's system prompt is rendered as SYSTEM.md,
|
||||
all agents are listed in AGENTS.md, and subagent .md files are deployed.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides for Pi subagents.
|
||||
Maps agent slug to model string, e.g.:
|
||||
{ chiron = "anthropic/claude-sonnet-4"; chiron-forge = "anthropic/claude-sonnet-4"; }
|
||||
'';
|
||||
};
|
||||
|
||||
externalSkills = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
src = mkOption {
|
||||
type = types.anything;
|
||||
description = "Flake input pointing to a skills repository root.";
|
||||
};
|
||||
skillsDir = mkOption {
|
||||
type = types.str;
|
||||
default = "skills";
|
||||
description = ''
|
||||
Subdirectory inside src that contains skill folders.
|
||||
'';
|
||||
};
|
||||
selectSkills = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
List of skill names to cherry-pick from this source.
|
||||
null means include every skill found in skillsDir.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
description = ''
|
||||
External skill sources passed to mkOpencodeSkills.
|
||||
Each entry maps directly to an element of the externalSkills
|
||||
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
||||
{ src = inputs.skills-vercel; }
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
primaryAgent = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Override which canonical agent is used as primary for SYSTEM.md.
|
||||
When null, the first agent with mode="primary" is used.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = types.attrsOf types.anything;
|
||||
options = {
|
||||
packages = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Pi packages to install (npm:, git:, or local paths).
|
||||
These are written to `${cfg.path}/settings.json`.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultProvider = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
|
||||
};
|
||||
|
||||
defaultModel = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default model ID.";
|
||||
};
|
||||
|
||||
defaultThinkingLevel = mkOption {
|
||||
type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
|
||||
default = null;
|
||||
description = "Default extended thinking level.";
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Pi theme name.";
|
||||
};
|
||||
|
||||
hideThinkingBlock = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
description = "Hide thinking blocks in output.";
|
||||
};
|
||||
|
||||
quietStartup = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
description = "Hide startup header.";
|
||||
};
|
||||
|
||||
compaction = mkOption {
|
||||
type = types.nullOr (types.submodule {
|
||||
options = {
|
||||
enabled = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
};
|
||||
reserveTokens = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
};
|
||||
keepRecentTokens = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null;
|
||||
description = "Auto-compaction settings.";
|
||||
};
|
||||
|
||||
enabledModels = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = "Model patterns for Ctrl+P cycling.";
|
||||
};
|
||||
|
||||
sessionDir = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Directory where session files are stored.";
|
||||
};
|
||||
|
||||
extensions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Local extension file paths or directories.";
|
||||
};
|
||||
|
||||
skills = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Local skill file paths or directories.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Pi settings written to `${cfg.path}/settings.json`.
|
||||
Only non-null values are included in the generated JSON.
|
||||
See pi docs/settings.md for all options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (let
|
||||
basePath = lib.removeSuffix "/" cfg.path;
|
||||
|
||||
# Build settings.json by filtering out null values recursively
|
||||
filterNulls = attrs:
|
||||
lib.filterAttrs (_: v: v != null) (
|
||||
builtins.mapAttrs (_: v:
|
||||
if builtins.isAttrs v
|
||||
then let
|
||||
filtered = filterNulls v;
|
||||
in
|
||||
if filtered == {}
|
||||
then null
|
||||
else filtered
|
||||
else v)
|
||||
attrs
|
||||
);
|
||||
|
||||
piSettings = filterNulls cfg.settings;
|
||||
|
||||
# Rendered agents (only computed when agentsInput is set)
|
||||
rendered =
|
||||
if cfg.agentsInput != null
|
||||
then
|
||||
(import ../../../../lib {inherit lib;}).agents.renderForPi {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
primaryAgent = cfg.primaryAgent;
|
||||
}
|
||||
else null;
|
||||
|
||||
# Dynamic home.file entries for agent .md files
|
||||
agentFiles =
|
||||
if cfg.agentsInput != null
|
||||
then let
|
||||
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
|
||||
in
|
||||
builtins.listToAttrs (
|
||||
map (name: {
|
||||
name = "${basePath}/agents/${name}.md";
|
||||
value = {source = "${rendered}/agents/${name}.md";};
|
||||
})
|
||||
agentNames
|
||||
)
|
||||
else {};
|
||||
in {
|
||||
home.file = mkMerge [
|
||||
# ── MCP servers from programs.mcp → ${cfg.path}/mcp.json ───────
|
||||
(mkIf (cfg.mcpServers != {}) {
|
||||
"${basePath}/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
|
||||
})
|
||||
|
||||
# ── ${cfg.path}/settings.json ──────────────────────────────────
|
||||
{
|
||||
"${basePath}/settings.json".text = builtins.toJSON piSettings;
|
||||
}
|
||||
|
||||
# ── AGENTS.md — agent descriptions and specialist listing ──────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
"${basePath}/AGENTS.md".source = "${rendered}/AGENTS.md";
|
||||
})
|
||||
|
||||
# ── SYSTEM.md — primary agent's system prompt ──────────────────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
"${basePath}/SYSTEM.md".source = "${rendered}/SYSTEM.md";
|
||||
})
|
||||
|
||||
# ── Agents — pi-subagents .md files ────────────────────────────
|
||||
agentFiles
|
||||
|
||||
# ── Skills symlinked from AGENTS repo ──────────────────────────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
"${basePath}/skills".source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||
inherit pkgs;
|
||||
customSkills = "${cfg.agentsInput}/skills";
|
||||
externalSkills =
|
||||
map (
|
||||
entry:
|
||||
{inherit (entry) src skillsDir;}
|
||||
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
||||
)
|
||||
cfg.externalSkills;
|
||||
};
|
||||
})
|
||||
];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Coding-related Home Manager modules
|
||||
{
|
||||
imports = [
|
||||
./editors.nix
|
||||
./opencode.nix
|
||||
./agents
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.editors;
|
||||
# home-manager 26.05+ renamed extraLuaConfig → initLua.
|
||||
# On stable 25.11 initLua does not exist; fall back to extraLuaConfig.
|
||||
hasInitLua = options.programs.neovim ? initLua;
|
||||
lazyVimConfig = ''
|
||||
-- Bootstrap lazy.nvim
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.loop.fs_stat(lazypath) then
|
||||
vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"https://github.com/folke/lazy.nvim.git",
|
||||
"--branch=stable",
|
||||
lazypath,
|
||||
})
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
-- Bootstrap LazyVim via lazy.nvim
|
||||
-- Docs: https://github.com/folke/lazy.nvim and https://www.lazyvim.org/
|
||||
require("lazy").setup({
|
||||
spec = {
|
||||
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
||||
{ import = "lazyvim.plugins.extras.lang.typescript" },
|
||||
{ import = "lazyvim.plugins.extras.lang.python" },
|
||||
{ import = "lazyvim.plugins.extras.lang.go" },
|
||||
{ import = "lazyvim.plugins.extras.lang.nix" },
|
||||
{ import = "lazyvim.plugins.extras.lang.rust" },
|
||||
{ import = "lazyvim.plugins.extras.lang.nushell" },
|
||||
{ "Mofiqul/dracula.nvim" },
|
||||
},
|
||||
defaults = { lazy = false, version = false },
|
||||
install = { colorscheme = { "dracula", "tokyonight", "habamax" } },
|
||||
checker = { enabled = false },
|
||||
performance = {
|
||||
rtp = {
|
||||
disabled_plugins = {
|
||||
"gzip", "tarPlugin", "tohtml", "tutor", "zipPlugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
vim.o.termguicolors = true
|
||||
vim.cmd.colorscheme("dracula")
|
||||
'';
|
||||
in {
|
||||
options.coding.editors = {
|
||||
neovim = {
|
||||
enable = mkEnableOption "neovim with LazyVim configuration";
|
||||
};
|
||||
zed = {
|
||||
enable = mkEnableOption "zed editor with custom configuration";
|
||||
};
|
||||
};
|
||||
config = mkMerge [
|
||||
# Neovim configuration
|
||||
(mkIf cfg.neovim.enable (mkMerge [
|
||||
{
|
||||
programs.neovim = {
|
||||
enable = true;
|
||||
defaultEditor = true;
|
||||
viAlias = true;
|
||||
vimAlias = true;
|
||||
vimdiffAlias = true;
|
||||
withNodeJs = true;
|
||||
withPython3 = true;
|
||||
};
|
||||
}
|
||||
# Use initLua on HM 26.05+ (unstable), extraLuaConfig on HM ≤ 25.11 (stable)
|
||||
(
|
||||
if hasInitLua
|
||||
then {programs.neovim.initLua = lazyVimConfig;}
|
||||
else {programs.neovim.extraLuaConfig = lazyVimConfig;}
|
||||
)
|
||||
]))
|
||||
# Zed editor configuration
|
||||
(mkIf cfg.zed.enable {
|
||||
programs.zed-editor = {
|
||||
enable = true;
|
||||
userSettings = {
|
||||
# UI and Theme
|
||||
theme = "Dracula";
|
||||
ui_font_size = 16;
|
||||
buffer_font_size = 16;
|
||||
buffer_font_family = "FiraCode Nerd Font";
|
||||
# Editor Behavior
|
||||
vim_mode = true;
|
||||
auto_update = false;
|
||||
format_on_save = "on";
|
||||
load_direnv = "shell_hook";
|
||||
# AI Features
|
||||
features = {
|
||||
edit_prediction_provider = "zed";
|
||||
};
|
||||
edit_predictions = {
|
||||
mode = "subtle";
|
||||
};
|
||||
show_edit_predictions = true;
|
||||
agent = {
|
||||
default_model = {
|
||||
provider = "zed.dev";
|
||||
model = "claude-sonnet-4";
|
||||
};
|
||||
};
|
||||
assistant = {
|
||||
version = "2";
|
||||
default_model = {
|
||||
provider = "anthropic";
|
||||
model = "claude-4";
|
||||
};
|
||||
};
|
||||
# Language Models
|
||||
language_models = {
|
||||
anthropic = {
|
||||
api_url = "https://api.anthropic.com";
|
||||
};
|
||||
openai = {
|
||||
api_url = "https://api.openai.com/v1";
|
||||
};
|
||||
ollama = {
|
||||
api_url = "http://localhost:11434";
|
||||
};
|
||||
};
|
||||
# Languages Configuration
|
||||
languages = {
|
||||
Nix = {
|
||||
language_servers = ["nixd"];
|
||||
formatter = {
|
||||
external = {
|
||||
command = "alejandra";
|
||||
arguments = [
|
||||
"-q"
|
||||
"-"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
Python = {
|
||||
language_servers = ["pyrefly"];
|
||||
formatter = {
|
||||
external = {
|
||||
command = "black";
|
||||
arguments = ["-"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
# LSP Configuration
|
||||
lsp = {
|
||||
rust-analyzer = {
|
||||
initialization_options = {
|
||||
check = {
|
||||
command = "clippy";
|
||||
};
|
||||
};
|
||||
};
|
||||
pyrefly = {
|
||||
binary = {
|
||||
arguments = ["--lsp"];
|
||||
};
|
||||
};
|
||||
};
|
||||
# Context Servers
|
||||
context_servers = {
|
||||
some-context-server = {
|
||||
source = "custom";
|
||||
command = "some-command";
|
||||
args = [
|
||||
"arg-1"
|
||||
"arg-2"
|
||||
];
|
||||
env = {};
|
||||
};
|
||||
};
|
||||
# Privacy
|
||||
telemetry = {
|
||||
metrics = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
# Common packages (always installed if either editor is enabled)
|
||||
(mkIf (cfg.neovim.enable || cfg.zed.enable) {
|
||||
home.packages = with pkgs; [zig];
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.opencode;
|
||||
in {
|
||||
options.coding.opencode = {
|
||||
enable = mkEnableOption "opencode AI coding assistant";
|
||||
|
||||
ohMyOpencodeSettings = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Attributes merged (via recursiveUpdate) on top of the default
|
||||
oh-my-opencode.json. Use this to set provider-specific model
|
||||
assignments per machine.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
agents.sisyphus.model = "anthropic/claude-opus-4-5";
|
||||
categories.ultrabrain.model = "anthropic/claude-opus-4-5";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraSettings = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Extra opencode settings merged (via mkMerge) into
|
||||
programs.opencode.settings. Use this to add provider
|
||||
configuration that is specific to a machine or organisation.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
provider.anthropic = {
|
||||
name = "Anthropic";
|
||||
models."claude-opus-4-5" = { limit.context = 200000; };
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraPlugins = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Additional opencode plugins to add to the plugin list.
|
||||
Each entry is a path or package name passed to opencode's plugin array.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
programs.opencode = {
|
||||
enable = true;
|
||||
enableMcpIntegration = true;
|
||||
settings = mkMerge [
|
||||
{
|
||||
theme = "opencode";
|
||||
plugin = ["oh-my-openagent"] ++ cfg.extraPlugins;
|
||||
formatter = {
|
||||
alejandra = {
|
||||
command = ["alejandra" "-q" "-"];
|
||||
extensions = [".nix"];
|
||||
};
|
||||
};
|
||||
}
|
||||
cfg.extraSettings
|
||||
];
|
||||
};
|
||||
|
||||
home.file.".config/opencode/oh-my-opencode.json".text = builtins.toJSON (
|
||||
recursiveUpdate
|
||||
{
|
||||
"$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json";
|
||||
google_auth = false;
|
||||
disabled_mcps = ["context7" "websearch"];
|
||||
}
|
||||
cfg.ohMyOpencodeSettings
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Home Manager modules organized by category
|
||||
{
|
||||
imports = [
|
||||
./cli
|
||||
./coding
|
||||
./ports.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
# Home Manager Module for Port Management
|
||||
#
|
||||
# This module provides centralized port management for Home Manager configurations.
|
||||
# Define ports once and use them consistently across user services, with support
|
||||
# for host-specific overrides.
|
||||
#
|
||||
# Usage in your Home Manager configuration:
|
||||
#
|
||||
# # In your home.nix or flake:
|
||||
# imports = [ inputs.m3ta-nixpkgs.homeManagerModules.default ];
|
||||
#
|
||||
# m3ta.ports = {
|
||||
# enable = true;
|
||||
#
|
||||
# # Define your default ports
|
||||
# definitions = {
|
||||
# vscodium = 8080;
|
||||
# jupyter = 8888;
|
||||
# dev-server = 3000;
|
||||
# local-api = 8000;
|
||||
# docs-preview = 4000;
|
||||
# };
|
||||
#
|
||||
# # Define host-specific overrides
|
||||
# hostOverrides = {
|
||||
# laptop = {
|
||||
# dev-server = 3001;
|
||||
# vscodium = 8081;
|
||||
# };
|
||||
# desktop = {
|
||||
# jupyter = 9999;
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# # Set the current hostname
|
||||
# currentHost = "laptop"; # Or use config.networking.hostName if available
|
||||
# };
|
||||
#
|
||||
# # Use ports in your configuration:
|
||||
# home.file.".config/myapp/config.json".text = builtins.toJSON {
|
||||
# port = config.m3ta.ports.get "dev-server";
|
||||
# };
|
||||
#
|
||||
# # Generate environment variables:
|
||||
# home.sessionVariables = {
|
||||
# DEV_SERVER_PORT = toString (config.m3ta.ports.get "dev-server");
|
||||
# JUPYTER_PORT = toString (config.m3ta.ports.get "jupyter");
|
||||
# };
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.ports;
|
||||
|
||||
portsLib = import ../../lib/ports.nix {inherit lib;};
|
||||
|
||||
portHelpers =
|
||||
if cfg.enable
|
||||
then
|
||||
portsLib.mkPortHelpers {
|
||||
ports = cfg.definitions;
|
||||
hostPorts = cfg.hostOverrides;
|
||||
}
|
||||
else null;
|
||||
in {
|
||||
options.m3ta.ports = {
|
||||
enable = mkEnableOption "centralized port management for Home Manager";
|
||||
|
||||
definitions = mkOption {
|
||||
type = types.attrsOf types.port;
|
||||
default = {};
|
||||
description = "Default port definitions for user services.";
|
||||
};
|
||||
|
||||
hostOverrides = mkOption {
|
||||
type = types.attrsOf (types.attrsOf types.port);
|
||||
default = {};
|
||||
description = "Host-specific port overrides.";
|
||||
};
|
||||
|
||||
currentHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Hostname to use for port resolution.";
|
||||
};
|
||||
|
||||
# Internal computed options
|
||||
get = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
getForHost = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
all = mkOption {
|
||||
type = types.attrsOf types.port;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
allForHost = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
services = mkOption {
|
||||
type = types.listOf types.str;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
# Env var generation
|
||||
generateEnvVars = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Generate environment variables for all ports.";
|
||||
};
|
||||
|
||||
envVarPrefix = mkOption {
|
||||
type = types.str;
|
||||
default = "PORT_";
|
||||
description = "Prefix for generated environment variables.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
m3ta.ports.get = service: portHelpers.getPort service cfg.currentHost;
|
||||
m3ta.ports.getForHost = host: service: portHelpers.getPort service host;
|
||||
m3ta.ports.all = portHelpers.getHostPorts cfg.currentHost;
|
||||
m3ta.ports.allForHost = portHelpers.getHostPorts;
|
||||
m3ta.ports.services = portHelpers.listServices;
|
||||
|
||||
home.sessionVariables = mkIf cfg.generateEnvVars (
|
||||
let
|
||||
toEnvVarName = service:
|
||||
cfg.envVarPrefix + (lib.toUpper (builtins.replaceStrings ["-"] ["_"] service));
|
||||
in
|
||||
builtins.listToAttrs (
|
||||
map (service: {
|
||||
name = toEnvVarName service;
|
||||
value = toString (cfg.get service);
|
||||
})
|
||||
cfg.services
|
||||
)
|
||||
);
|
||||
|
||||
home.file.".config/m3ta/ports.json".text = builtins.toJSON {
|
||||
hostname = cfg.currentHost;
|
||||
ports = cfg.all;
|
||||
allDefinitions = cfg.definitions;
|
||||
hostOverrides = cfg.hostOverrides;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# NixOS Modules
|
||||
# Import this in your NixOS configuration with:
|
||||
# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ];
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
# This is the main entry point for all custom NixOS modules
|
||||
# Add your custom modules here as imports or inline definitions
|
||||
|
||||
imports = [
|
||||
./mem0.nix
|
||||
./ports.nix
|
||||
./pi-agent.nix
|
||||
# Example: ./my-service.nix
|
||||
# Add more module files here as you create them
|
||||
];
|
||||
|
||||
# You can also define inline options here
|
||||
# options = {
|
||||
# m3ta = {
|
||||
# # Your custom options
|
||||
# };
|
||||
# };
|
||||
|
||||
# config = {
|
||||
# # Your custom configuration
|
||||
# };
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
# NixOS Module for Mem0 REST API Server
|
||||
#
|
||||
# This module provides a systemd service for the Mem0 REST API server,
|
||||
# allowing you to run mem0 as a system service with configurable vector storage.
|
||||
#
|
||||
# Usage in your NixOS configuration:
|
||||
#
|
||||
# # In your flake.nix or configuration.nix:
|
||||
# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ];
|
||||
#
|
||||
# m3ta.mem0 = {
|
||||
# enable = true;
|
||||
# port = 8000;
|
||||
# host = "127.0.0.1";
|
||||
#
|
||||
# # LLM Configuration
|
||||
# llm = {
|
||||
# provider = "openai";
|
||||
# apiKeyFile = "/run/secrets/openai-api-key"; # Use agenix or sops-nix
|
||||
# model = "gpt-4";
|
||||
# };
|
||||
#
|
||||
# # Vector Storage Configuration
|
||||
# vectorStore = {
|
||||
# provider = "qdrant"; # or "chroma", "pinecone", etc.
|
||||
# config = {
|
||||
# host = "localhost";
|
||||
# port = 6333;
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# # Optional: Environment variables
|
||||
# environmentFile = "/etc/mem0/environment";
|
||||
# };
|
||||
#
|
||||
# Using with m3ta.ports (recommended):
|
||||
#
|
||||
# m3ta.ports = {
|
||||
# enable = true;
|
||||
# definitions = { mem0 = 8000; };
|
||||
# hostOverrides.laptop = { mem0 = 8080; };
|
||||
# currentHost = config.networking.hostName;
|
||||
# };
|
||||
#
|
||||
# m3ta.mem0 = {
|
||||
# enable = true;
|
||||
# port = config.m3ta.ports.get "mem0"; # Automatically uses host-specific port
|
||||
# };
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.mem0;
|
||||
|
||||
# Python environment with mem0
|
||||
pythonEnv = pkgs.python3.withPackages (ps:
|
||||
with ps; [
|
||||
cfg.package
|
||||
]);
|
||||
|
||||
# Convert vector store config to environment variables
|
||||
vectorStoreEnv =
|
||||
if cfg.vectorStore.provider == "qdrant"
|
||||
then {
|
||||
MEM0_VECTOR_PROVIDER = "qdrant";
|
||||
QDRANT_HOST = cfg.vectorStore.config.host or "localhost";
|
||||
QDRANT_PORT = toString (cfg.vectorStore.config.port or 6333);
|
||||
QDRANT_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories";
|
||||
}
|
||||
else if cfg.vectorStore.provider == "pgvector"
|
||||
then {
|
||||
MEM0_VECTOR_PROVIDER = "pgvector";
|
||||
POSTGRES_HOST = cfg.vectorStore.config.host or "localhost";
|
||||
POSTGRES_PORT = toString (cfg.vectorStore.config.port or 5432);
|
||||
POSTGRES_DB = cfg.vectorStore.config.dbname or "postgres";
|
||||
POSTGRES_USER = cfg.vectorStore.config.user or "postgres";
|
||||
POSTGRES_PASSWORD = cfg.vectorStore.config.password or "postgres";
|
||||
POSTGRES_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories";
|
||||
}
|
||||
else if cfg.vectorStore.provider == "chroma"
|
||||
then {
|
||||
MEM0_VECTOR_PROVIDER = "chroma";
|
||||
CHROMA_HOST = cfg.vectorStore.config.host or "localhost";
|
||||
CHROMA_PORT = toString (cfg.vectorStore.config.port or 8000);
|
||||
CHROMA_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories";
|
||||
}
|
||||
else {};
|
||||
|
||||
# Start script that sets up environment and runs the server
|
||||
startScript = pkgs.writeShellScript "mem0-start" ''
|
||||
set -e
|
||||
|
||||
# Load environment file if specified
|
||||
${optionalString (cfg.environmentFile != null) ''
|
||||
if [ -f "${cfg.environmentFile}" ]; then
|
||||
set -a
|
||||
source "${cfg.environmentFile}"
|
||||
set +a
|
||||
fi
|
||||
''}
|
||||
|
||||
# Load API key from file if specified
|
||||
${optionalString (cfg.llm.apiKeyFile != null) ''
|
||||
if [ -f "${cfg.llm.apiKeyFile}" ]; then
|
||||
export OPENAI_API_KEY="$(cat ${cfg.llm.apiKeyFile})"
|
||||
fi
|
||||
''}
|
||||
|
||||
# Create state directory
|
||||
mkdir -p ${cfg.stateDir}
|
||||
cd ${cfg.stateDir}
|
||||
|
||||
# Run the server
|
||||
exec ${pythonEnv}/bin/mem0-server
|
||||
'';
|
||||
in {
|
||||
options.m3ta.mem0 = {
|
||||
enable = mkEnableOption "Mem0 REST API server";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.mem0;
|
||||
defaultText = literalExpression "pkgs.mem0";
|
||||
description = "The mem0 package to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Host address to bind the server to.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8000;
|
||||
description = "Port to run the REST API server on.";
|
||||
};
|
||||
|
||||
workers = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = "Number of worker processes.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum ["critical" "error" "warning" "info" "debug" "trace"];
|
||||
default = "info";
|
||||
description = "Logging level for the server.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/mem0";
|
||||
description = "Directory to store mem0 data and state.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "mem0";
|
||||
description = "User account under which mem0 runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "mem0";
|
||||
description = "Group under which mem0 runs.";
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Environment file containing additional configuration.
|
||||
This file should contain KEY=value pairs, one per line.
|
||||
Useful for secrets that shouldn't be in the Nix store.
|
||||
'';
|
||||
example = "/etc/mem0/environment";
|
||||
};
|
||||
|
||||
# LLM Configuration
|
||||
llm = {
|
||||
provider = mkOption {
|
||||
type = types.enum ["openai" "anthropic" "azure" "groq" "together" "ollama" "litellm"];
|
||||
default = "openai";
|
||||
description = "LLM provider to use for memory operations.";
|
||||
};
|
||||
|
||||
model = mkOption {
|
||||
type = types.str;
|
||||
default = "gpt-4o-mini";
|
||||
description = "Model name to use for the LLM.";
|
||||
};
|
||||
|
||||
apiKeyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to file containing the API key for the LLM provider.
|
||||
The file should contain only the API key.
|
||||
This is more secure than putting the key in the Nix store.
|
||||
'';
|
||||
example = "/run/secrets/openai-api-key";
|
||||
};
|
||||
|
||||
temperature = mkOption {
|
||||
type = types.nullOr types.float;
|
||||
default = null;
|
||||
description = "Temperature parameter for LLM generation.";
|
||||
};
|
||||
|
||||
maxTokens = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = "Maximum tokens for LLM generation.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = "Additional LLM configuration options.";
|
||||
example = {
|
||||
top_p = 1.0;
|
||||
frequency_penalty = 0.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Vector Store Configuration
|
||||
vectorStore = {
|
||||
provider = mkOption {
|
||||
type = types.enum [
|
||||
"qdrant"
|
||||
"chroma"
|
||||
"pinecone"
|
||||
"weaviate"
|
||||
"faiss"
|
||||
"pgvector"
|
||||
"redis"
|
||||
"elasticsearch"
|
||||
"milvus"
|
||||
];
|
||||
default = "qdrant";
|
||||
description = "Vector database provider to use.";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Configuration for the vector store.
|
||||
The structure depends on the provider.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
host = "localhost";
|
||||
port = 6333;
|
||||
collection_name = "mem0_memories";
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Embedder Configuration
|
||||
embedder = {
|
||||
provider = mkOption {
|
||||
type = types.nullOr (types.enum ["openai" "huggingface" "ollama" "vertexai"]);
|
||||
default = null;
|
||||
description = "Embedding model provider. If null, uses default.";
|
||||
};
|
||||
|
||||
model = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Embedding model name to use.";
|
||||
example = "text-embedding-3-small";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = "Configuration for the embedder.";
|
||||
example = {
|
||||
model = "text-embedding-3-small";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Create user and group
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
description = "Mem0 service user";
|
||||
home = cfg.stateDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
|
||||
# Systemd service
|
||||
systemd.services.mem0 = {
|
||||
description = "Mem0 REST API Server";
|
||||
after = ["network.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = startScript;
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ReadWritePaths = [cfg.stateDir];
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false; # Python needs this
|
||||
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
|
||||
};
|
||||
|
||||
environment =
|
||||
{
|
||||
PYTHONUNBUFFERED = "1";
|
||||
MEM0_HOST = cfg.host;
|
||||
MEM0_PORT = toString cfg.port;
|
||||
MEM0_LLM_PROVIDER = cfg.llm.provider;
|
||||
MEM0_LLM_MODEL = cfg.llm.model;
|
||||
MEM0_HISTORY_DB_PATH = "${cfg.stateDir}/history.db";
|
||||
MEM0_WORKERS = toString cfg.workers;
|
||||
MEM0_LOG_LEVEL = cfg.logLevel;
|
||||
}
|
||||
// optionalAttrs (cfg.llm.temperature != null) {
|
||||
MEM0_LLM_TEMPERATURE = toString cfg.llm.temperature;
|
||||
}
|
||||
// optionalAttrs (cfg.llm.extraConfig != {}) {
|
||||
MEM0_LLM_EXTRA_CONFIG = builtins.toJSON cfg.llm.extraConfig;
|
||||
}
|
||||
// optionalAttrs (cfg.embedder.provider != null) {
|
||||
MEM0_EMBEDDER_PROVIDER = cfg.embedder.provider;
|
||||
}
|
||||
// optionalAttrs (cfg.embedder.model != null) {
|
||||
MEM0_EMBEDDER_MODEL = cfg.embedder.model;
|
||||
}
|
||||
// vectorStoreEnv;
|
||||
};
|
||||
|
||||
# Open firewall port if binding to non-localhost
|
||||
networking.firewall.allowedTCPPorts = mkIf (cfg.host != "127.0.0.1" && cfg.host != "localhost") [cfg.port];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
{cfg, pkgs, lib, ...}:
|
||||
with lib; let
|
||||
managedSettingsFile = pkgs.writeText "pi-agent-managed-settings.json" (builtins.toJSON cfg.settings);
|
||||
|
||||
managedEnvFile =
|
||||
pkgs.writeText "pi-agent-managed.env"
|
||||
(concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${v}") cfg.environment));
|
||||
|
||||
runtimePath = concatStringsSep ":" (
|
||||
[
|
||||
"${cfg.package}/bin"
|
||||
"${pkgs.nodejs}/bin"
|
||||
"${pkgs.git}/bin"
|
||||
"${pkgs.coreutils}/bin"
|
||||
"${pkgs.findutils}/bin"
|
||||
"${pkgs.gnugrep}/bin"
|
||||
"${pkgs.gnused}/bin"
|
||||
"${pkgs.util-linux}/bin"
|
||||
"/run/current-system/sw/bin"
|
||||
]
|
||||
++ map (p: "${p}/bin") cfg.extraPackages
|
||||
);
|
||||
|
||||
userPolicyCase = concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
user: userCfg: ''
|
||||
${escapeShellArg user})
|
||||
USER_CONFIG_PATH=${escapeShellArg (
|
||||
if userCfg.configPath != null
|
||||
then userCfg.configPath
|
||||
else cfg.wrapper.hostConfigPath
|
||||
)}
|
||||
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
||||
;;
|
||||
''
|
||||
)
|
||||
cfg.hostUsers
|
||||
);
|
||||
in
|
||||
pkgs.writeShellScriptBin cfg.wrapper.runnerName ''
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "${cfg.wrapper.runnerName} must run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "Usage: ${cfg.wrapper.runnerName} <invoking-user> <cwd> [pi-args...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
invoking_user="$1"
|
||||
shift
|
||||
cwd="$1"
|
||||
shift
|
||||
|
||||
resolve_user_policy() {
|
||||
local user="$1"
|
||||
USER_CONFIG_PATH=""
|
||||
USER_ROOTS=()
|
||||
case "$user" in
|
||||
${userPolicyCase}
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! resolve_user_policy "$invoking_user"; then
|
||||
echo "User '$invoking_user' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
user_home="$(eval echo "~$invoking_user")"
|
||||
if [ -z "$user_home" ] || [ "$user_home" = "~$invoking_user" ]; then
|
||||
echo "Unable to determine home directory for user '$invoking_user'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expand_home_path() {
|
||||
local input="$1"
|
||||
if [ "$input" = "~" ]; then
|
||||
printf '%s\n' "$user_home"
|
||||
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
||||
printf '%s\n' "$user_home/''${input:2}"
|
||||
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
||||
printf '%s\n' "$input"
|
||||
else
|
||||
# Bare relative path → resolve from user's home
|
||||
printf '%s\n' "$user_home/$input"
|
||||
fi
|
||||
}
|
||||
|
||||
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$cwd")"
|
||||
|
||||
resolved_roots=()
|
||||
skipped_roots=()
|
||||
is_allowed_cwd=0
|
||||
for configured_root in "''${USER_ROOTS[@]}"; do
|
||||
expanded_root="$(expand_home_path "$configured_root")"
|
||||
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
||||
if [ ! -d "$resolved_root" ]; then
|
||||
skipped_roots+=("$resolved_root")
|
||||
continue
|
||||
fi
|
||||
resolved_roots+=("$resolved_root")
|
||||
case "$cwd_real/" in
|
||||
"$resolved_root"/*)
|
||||
is_allowed_cwd=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
||||
echo "Denied: no valid existing project roots are configured for user '$invoking_user'." >&2
|
||||
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
||||
echo "Configured but missing roots:" >&2
|
||||
for root in "''${skipped_roots[@]}"; do
|
||||
echo " - $root" >&2
|
||||
done
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$is_allowed_cwd" -ne 1 ]; then
|
||||
echo "Denied: '$cwd_real' is outside allowed project roots for user '$invoking_user'." >&2
|
||||
echo "Allowed roots:" >&2
|
||||
for root in "''${resolved_roots[@]}"; do
|
||||
echo " - $root" >&2
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} \
|
||||
${escapeShellArg cfg.stateDir} \
|
||||
${escapeShellArg "${cfg.stateDir}/.pi"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.pi/agent"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.pi/agent/sessions"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.project-mounts"} \
|
||||
${escapeShellArg "${cfg.stateDir}/projects"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.npm"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.npm-global"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.npm-global/bin"} \
|
||||
${escapeShellArg "${cfg.stateDir}/.npm-global/lib"}
|
||||
|
||||
config_source="$USER_CONFIG_PATH"
|
||||
if ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$config_source"; then
|
||||
source_dir="$config_source"
|
||||
else
|
||||
source_dir="$(expand_home_path "$config_source")"
|
||||
fi
|
||||
|
||||
|
||||
if [ "${
|
||||
if cfg.wrapper.syncConfigFromHost
|
||||
then "1"
|
||||
else "0"
|
||||
}" = "1" ] && [ -d "$source_dir" ]; then
|
||||
${pkgs.rsync}/bin/rsync -a --delete \
|
||||
--exclude='auth.json' \
|
||||
--exclude='mcp-oauth' \
|
||||
--exclude='sessions' \
|
||||
--exclude='bin' \
|
||||
--exclude='mcp-cache.json' \
|
||||
"$source_dir/" ${escapeShellArg "${cfg.stateDir}/.pi/agent/"}
|
||||
${pkgs.coreutils}/bin/chown -R ${escapeShellArg "${cfg.user}:${cfg.group}"} ${escapeShellArg "${cfg.stateDir}/.pi/agent"}
|
||||
fi
|
||||
|
||||
# Merge host settings.json (if any) with Nix-managed settings.
|
||||
# Precedence: host settings first, Nix-managed keys override recursively.
|
||||
settings_target=${escapeShellArg "${cfg.stateDir}/.pi/agent/settings.json"}
|
||||
${pkgs.python3}/bin/python3 - "$settings_target" ${escapeShellArg managedSettingsFile} <<'PY_PI_SETTINGS_MERGE'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def load_obj(path):
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def deep_merge(base, override):
|
||||
if isinstance(base, dict) and isinstance(override, dict):
|
||||
out = dict(base)
|
||||
for key, value in override.items():
|
||||
out[key] = deep_merge(out.get(key), value)
|
||||
return out
|
||||
return override
|
||||
|
||||
|
||||
def main():
|
||||
target = sys.argv[1]
|
||||
managed = sys.argv[2]
|
||||
base_obj = load_obj(target)
|
||||
managed_obj = load_obj(managed)
|
||||
merged = deep_merge(base_obj, managed_obj)
|
||||
|
||||
os.makedirs(os.path.dirname(target), exist_ok=True)
|
||||
tmp = f"{target}.tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(merged, f, indent=2, sort_keys=True)
|
||||
f.write("\n")
|
||||
os.replace(tmp, target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
PY_PI_SETTINGS_MERGE
|
||||
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$settings_target"
|
||||
${pkgs.coreutils}/bin/chmod 0640 "$settings_target"
|
||||
|
||||
# Merge environment into isolated .env with precedence:
|
||||
# 1) synced host env (source_dir/.env)
|
||||
# 2) Nix-managed environment attrset
|
||||
# 3) Nix-managed environmentFiles (appended in declaration order)
|
||||
env_target=${escapeShellArg "${cfg.stateDir}/.pi/.env"}
|
||||
${pkgs.coreutils}/bin/install -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} -m 0640 /dev/null "$env_target"
|
||||
|
||||
if [ -f "$source_dir/.env" ]; then
|
||||
${pkgs.coreutils}/bin/cat "$source_dir/.env" >> "$env_target"
|
||||
printf '\n' >> "$env_target"
|
||||
fi
|
||||
|
||||
if [ -f ${escapeShellArg managedEnvFile} ]; then
|
||||
${pkgs.coreutils}/bin/cat ${escapeShellArg managedEnvFile} >> "$env_target"
|
||||
printf '\n' >> "$env_target"
|
||||
fi
|
||||
|
||||
${concatStringsSep "\n" (map (f: ''
|
||||
if [ -f ${escapeShellArg f} ]; then
|
||||
${pkgs.coreutils}/bin/cat ${escapeShellArg f} >> "$env_target"
|
||||
printf '\n' >> "$env_target"
|
||||
fi
|
||||
'')
|
||||
cfg.environmentFiles)}
|
||||
|
||||
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$env_target"
|
||||
${pkgs.coreutils}/bin/chmod 0640 "$env_target"
|
||||
|
||||
npm_prefix=${escapeShellArg "${cfg.stateDir}/.npm-global"}
|
||||
runtime_path=${escapeShellArg runtimePath}
|
||||
|
||||
project_mount_dir=${escapeShellArg "${cfg.stateDir}/.project-mounts"}
|
||||
project_links_dir=${escapeShellArg "${cfg.stateDir}/projects"}
|
||||
project_bind_pairs=()
|
||||
|
||||
matched_root=""
|
||||
matched_mount=""
|
||||
project_index=0
|
||||
|
||||
for root in "''${resolved_roots[@]}"; do
|
||||
if [ ! -d "$root" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
root_slug="$(printf '%s' "$root" | ${pkgs.gnused}/bin/sed 's#^/##; s#/#-#g; s#-\{2,\}#-#g; s#-$##; s#^$#root#')"
|
||||
root_slug="''${project_index}-''${root_slug}"
|
||||
project_index=$((project_index + 1))
|
||||
|
||||
mount_point="''${project_mount_dir}/''${root_slug}"
|
||||
link_path="''${project_links_dir}/''${root_slug}"
|
||||
|
||||
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} "$mount_point"
|
||||
${pkgs.coreutils}/bin/ln -sfn "$mount_point" "$link_path"
|
||||
|
||||
project_bind_pairs+=("$root:$mount_point")
|
||||
|
||||
case "$cwd_real/" in
|
||||
"$root"/*)
|
||||
if [ -z "$matched_root" ] || [ "''${#root}" -gt "''${#matched_root}" ]; then
|
||||
matched_root="$root"
|
||||
matched_mount="$mount_point"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$matched_root" ]; then
|
||||
echo "Failed to map cwd '$cwd_real' to an allowed root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$cwd_real" = "$matched_root" ]; then
|
||||
mapped_cwd="$matched_mount"
|
||||
else
|
||||
rel_path="''${cwd_real#"$matched_root/"}"
|
||||
mapped_cwd="$matched_mount/$rel_path"
|
||||
fi
|
||||
|
||||
pi_bin=${escapeShellArg "${cfg.package}/bin/${cfg.binaryName}"}
|
||||
|
||||
if [ ! -x "$pi_bin" ]; then
|
||||
for candidate in pi pi-agent; do
|
||||
alt=${escapeShellArg "${cfg.package}/bin"}/$candidate
|
||||
if [ -x "$alt" ]; then
|
||||
pi_bin="$alt"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ! -x "$pi_bin" ]; then
|
||||
echo "Pi binary not found or not executable: $pi_bin" >&2
|
||||
echo "Available executables in ${cfg.package}/bin:" >&2
|
||||
${pkgs.coreutils}/bin/ls -1 ${escapeShellArg "${cfg.package}/bin"} >&2 || true
|
||||
exit 127
|
||||
fi
|
||||
|
||||
cmd=(
|
||||
${pkgs.systemd}/bin/systemd-run
|
||||
--collect
|
||||
--wait
|
||||
--pty
|
||||
--service-type=exec
|
||||
-p User=${cfg.user}
|
||||
-p Group=${cfg.group}
|
||||
-p WorkingDirectory="$mapped_cwd"
|
||||
-p NoNewPrivileges=yes
|
||||
-p PrivateTmp=yes
|
||||
-p ProtectSystem=strict
|
||||
-p ProtectHome=false
|
||||
-p ProtectControlGroups=yes
|
||||
-p ProtectKernelTunables=yes
|
||||
-p ProtectKernelModules=yes
|
||||
-p RestrictSUIDSGID=yes
|
||||
-p LockPersonality=yes
|
||||
-p RestrictRealtime=yes
|
||||
-p RestrictNamespaces=yes
|
||||
-p MemoryDenyWriteExecute=no
|
||||
-p UMask=0007
|
||||
-p ReadWritePaths=${cfg.stateDir}
|
||||
-p EnvironmentFile=${cfg.stateDir}/.pi/.env
|
||||
-E HOME=${cfg.stateDir}
|
||||
-E PI_HOME=${cfg.stateDir}/.pi
|
||||
-E MESSAGING_CWD="$mapped_cwd"
|
||||
-E PATH="$runtime_path"
|
||||
-E NPM_CONFIG_CACHE=${cfg.stateDir}/.npm
|
||||
-E NPM_CONFIG_PREFIX="$npm_prefix"
|
||||
-E PI_AGENT_INVOKING_USER="$invoking_user"
|
||||
)
|
||||
|
||||
${optionalString (cfg.projectGroup != null) ''
|
||||
cmd+=( -p SupplementaryGroups=${cfg.projectGroup} )
|
||||
''}
|
||||
|
||||
# Only mark existing top-level paths inaccessible; systemd fails namespace
|
||||
# setup if InaccessiblePaths points to a non-existent path on this host.
|
||||
for p in /home /root /mnt /media /srv; do
|
||||
if [ -e "$p" ]; then
|
||||
cmd+=( -p "InaccessiblePaths=$p" )
|
||||
fi
|
||||
done
|
||||
|
||||
for pair in "''${project_bind_pairs[@]}"; do
|
||||
src="''${pair%%:*}"
|
||||
dst="''${pair#*:}"
|
||||
cmd+=( -p "BindPaths=$src:$dst" )
|
||||
done
|
||||
|
||||
${concatStringsSep "\n" (mapAttrsToList (name: value: ''cmd+=( -E ${escapeShellArg "${name}=${value}"} )'') cfg.wrapper.extraEnvironment)}
|
||||
|
||||
cmd+=( "$pi_bin" )
|
||||
${concatStringsSep "\n" (map (arg: ''cmd+=( ${escapeShellArg arg} )'') cfg.wrapper.extraRunArgs)}
|
||||
cmd+=( "$@" )
|
||||
|
||||
exec "''${cmd[@]}"
|
||||
''
|
||||
@@ -0,0 +1,92 @@
|
||||
{cfg, pkgs, lib, runner, ...}:
|
||||
with lib;
|
||||
pkgs.writeShellScriptBin cfg.wrapper.commandName ''
|
||||
set -euo pipefail
|
||||
|
||||
user_name="$(id -un)"
|
||||
user_home="$(eval echo "~$user_name")"
|
||||
if [ -z "$user_home" ] || [ "$user_home" = "~$user_name" ]; then
|
||||
user_home="$HOME"
|
||||
fi
|
||||
|
||||
resolve_user_policy() {
|
||||
local user="$1"
|
||||
USER_ROOTS=()
|
||||
case "$user" in
|
||||
${concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
user: userCfg: ''
|
||||
${escapeShellArg user})
|
||||
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
||||
;;
|
||||
''
|
||||
)
|
||||
cfg.hostUsers
|
||||
)}
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! resolve_user_policy "$user_name"; then
|
||||
echo "User '$user_name' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expand_home_path() {
|
||||
local input="$1"
|
||||
if [ "$input" = "~" ]; then
|
||||
printf '%s\n' "$user_home"
|
||||
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
||||
printf '%s\n' "$user_home/''${input:2}"
|
||||
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
||||
printf '%s\n' "$input"
|
||||
else
|
||||
printf '%s\n' "$user_home/$input"
|
||||
fi
|
||||
}
|
||||
|
||||
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$PWD")"
|
||||
|
||||
is_allowed_cwd=0
|
||||
resolved_roots=()
|
||||
skipped_roots=()
|
||||
for configured_root in "''${USER_ROOTS[@]}"; do
|
||||
expanded_root="$(expand_home_path "$configured_root")"
|
||||
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
||||
if [ ! -d "$resolved_root" ]; then
|
||||
skipped_roots+=("$resolved_root")
|
||||
continue
|
||||
fi
|
||||
resolved_roots+=("$resolved_root")
|
||||
case "$cwd_real/" in
|
||||
"$resolved_root"/*)
|
||||
is_allowed_cwd=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
||||
echo "Denied: no valid existing project roots are configured for user '$user_name'." >&2
|
||||
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
||||
echo "Configured but missing roots:" >&2
|
||||
for root in "''${skipped_roots[@]}"; do
|
||||
echo " - $root" >&2
|
||||
done
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$is_allowed_cwd" -ne 1 ]; then
|
||||
echo "Denied: '$cwd_real' is outside allowed project roots for user '$user_name'." >&2
|
||||
echo "Allowed roots:" >&2
|
||||
for root in "''${resolved_roots[@]}"; do
|
||||
echo " - $root" >&2
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec /run/wrappers/bin/sudo --non-interactive ${runner}/bin/${cfg.wrapper.runnerName} "$user_name" "$cwd_real" "$@"
|
||||
''
|
||||
@@ -0,0 +1,285 @@
|
||||
# NixOS Module for isolated Pi execution (fresh design)
|
||||
#
|
||||
# Goals:
|
||||
# - Dedicated isolated runtime identity (pi-agent user/group)
|
||||
# - Host UX via `pi` wrapper command
|
||||
# - Per-host-user project allowlists (different roots per user)
|
||||
# - No container mode
|
||||
# - Merge user Pi config + Nix-managed settings/env into isolated runtime
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.pi-agent;
|
||||
|
||||
hostUserNames = attrNames cfg.hostUsers;
|
||||
|
||||
runner = import ./pi-agent-runner.nix {inherit cfg pkgs lib;};
|
||||
wrapper = import ./pi-agent-wrapper.nix {inherit cfg pkgs lib runner;};
|
||||
in {
|
||||
options.m3ta.pi-agent = {
|
||||
enable = mkEnableOption "isolated Pi execution with dedicated system user and policy-enforced wrapper";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.pi-coding-agent;
|
||||
defaultText = literalExpression "pkgs.pi-coding-agent";
|
||||
description = "Pi package providing the executable used in isolated runtime.";
|
||||
};
|
||||
|
||||
binaryName = mkOption {
|
||||
type = types.str;
|
||||
default = "pi-agent";
|
||||
description = "Preferred executable name inside `${cfg.package}/bin` (falls back to pi/pi-agent auto-detection).";
|
||||
example = "pi";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "pi-agent";
|
||||
description = "System user that executes Pi in isolated mode.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "pi-agent";
|
||||
description = "System group for the isolated Pi user.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/pi-agent";
|
||||
description = "Writable state/home directory for isolated Pi runtime.";
|
||||
};
|
||||
|
||||
createUser = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to create the dedicated Pi user/group automatically.";
|
||||
};
|
||||
|
||||
hostUsers = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
projectRoots = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Allowed project roots for this host user.
|
||||
`~` and `~/...` are expanded relative to that host user's home.
|
||||
'';
|
||||
example = ["~/p" "~/work/client-a"];
|
||||
};
|
||||
|
||||
configPath = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Optional host path for this user's Pi config source. If null,
|
||||
wrapper.hostConfigPath is used. Relative paths resolve from the
|
||||
host user's home.
|
||||
'';
|
||||
example = ".pi/agent";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
description = ''
|
||||
Per-host-user policy map. Keys are host usernames.
|
||||
Each user defines their own allowed project roots and optional config source.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
m3tam3re = {
|
||||
projectRoots = [ "~/p" "~/src/private" ];
|
||||
configPath = ".pi/agent";
|
||||
};
|
||||
teammate = {
|
||||
projectRoots = [ "~/projects" ];
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = {};
|
||||
description = ''
|
||||
Nix-managed Pi settings merged into isolated `${cfg.stateDir}/.pi/agent/settings.json`.
|
||||
Merge precedence: synced host settings first, Nix-managed values override recursively.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
defaultModel = "anthropic/claude-sonnet-4";
|
||||
defaultProvider = "anthropic";
|
||||
quietStartup = true;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
environment = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Non-secret Nix-managed environment variables appended into isolated
|
||||
`${cfg.stateDir}/.pi/.env` after synced host values.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFiles = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Paths to env files (secrets/tokens) appended to isolated `${cfg.stateDir}/.pi/.env`
|
||||
after `environment` entries.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
description = "Extra packages added to isolated runtime PATH.";
|
||||
};
|
||||
|
||||
projectGroup = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
When set, the pi-agent user is added to this group and the group is
|
||||
passed as SupplementaryGroups to the systemd-run sandbox. This allows
|
||||
pi-agent to write to project directories that grant group write access.
|
||||
The user must ensure project directories have appropriate group ownership
|
||||
and permissions (e.g. setgid + group write).
|
||||
'';
|
||||
example = "users";
|
||||
};
|
||||
|
||||
wrapper = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable host-side wrapper command that enforces policy and runs isolated Pi.";
|
||||
};
|
||||
|
||||
commandName = mkOption {
|
||||
type = types.str;
|
||||
default = "pi";
|
||||
description = "Host wrapper command name.";
|
||||
};
|
||||
|
||||
runnerName = mkOption {
|
||||
type = types.str;
|
||||
default = "m3ta-pi-agent-runner";
|
||||
description = "Privileged runner command invoked via scoped sudo rule.";
|
||||
};
|
||||
|
||||
hideDirectBinary = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
When true and wrapper is enabled, do not add the raw Pi package to host PATH,
|
||||
reducing bypass risk by making wrapper the canonical entrypoint.
|
||||
'';
|
||||
};
|
||||
|
||||
syncConfigFromHost = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Sync host Pi config directory into isolated `${cfg.stateDir}/.pi/agent`
|
||||
on each invocation.
|
||||
'';
|
||||
};
|
||||
|
||||
hostConfigPath = mkOption {
|
||||
type = types.str;
|
||||
default = ".pi/agent";
|
||||
description = ''
|
||||
Default source path for host Pi config sync. Relative paths resolve from
|
||||
the invoking user's home. Per-user hostUsers.<name>.configPath overrides this.
|
||||
'';
|
||||
};
|
||||
|
||||
extraRunArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Extra arguments inserted before user-provided Pi args.";
|
||||
};
|
||||
|
||||
extraEnvironment = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = "Additional environment variables passed to isolated Pi runtime.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion = cfg.hostUsers != {};
|
||||
message = "m3ta.pi-agent.hostUsers must define at least one authorized host user.";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.wrapper.enable) || (cfg.hostUsers != {});
|
||||
message = "m3ta.pi-agent.hostUsers must not be empty when wrapper is enabled.";
|
||||
}
|
||||
]
|
||||
++ mapAttrsToList (user: userCfg: {
|
||||
assertion = userCfg.projectRoots != [];
|
||||
message = "m3ta.pi-agent.hostUsers.${user}.projectRoots must not be empty.";
|
||||
})
|
||||
cfg.hostUsers;
|
||||
|
||||
users.groups = mkIf cfg.createUser {
|
||||
"${cfg.group}" = {};
|
||||
};
|
||||
|
||||
users.users = mkIf cfg.createUser {
|
||||
"${cfg.user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
extraGroups = mkIf (cfg.projectGroup != null) [cfg.projectGroup];
|
||||
description = "Isolated Pi agent user";
|
||||
home = cfg.stateDir;
|
||||
createHome = true;
|
||||
shell = pkgs.bashInteractive;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.stateDir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.pi 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.pi/agent 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.pi/agent/sessions 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.project-mounts 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/projects 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.npm 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.npm-global 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.npm-global/bin 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.stateDir}/.npm-global/lib 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
# Wrapper is canonical when enabled; raw package on PATH is optional and
|
||||
# disabled by default to reduce bypass opportunities.
|
||||
environment.systemPackages =
|
||||
optional cfg.wrapper.enable wrapper
|
||||
++ optional ((!cfg.wrapper.enable) || (!cfg.wrapper.hideDirectBinary)) cfg.package;
|
||||
|
||||
security.sudo.extraRules = mkIf (cfg.wrapper.enable && hostUserNames != []) [
|
||||
{
|
||||
users = hostUserNames;
|
||||
commands = [
|
||||
{
|
||||
command = "${runner}/bin/${cfg.wrapper.runnerName}";
|
||||
options = ["NOPASSWD"];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
# NixOS Module for Port Management
|
||||
#
|
||||
# This module provides centralized port management across your NixOS systems.
|
||||
# Define ports once and use them consistently across all services, with
|
||||
# support for host-specific overrides.
|
||||
#
|
||||
# Usage in your NixOS configuration:
|
||||
#
|
||||
# # In your flake.nix or configuration.nix:
|
||||
# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ];
|
||||
#
|
||||
# m3ta.ports = {
|
||||
# enable = true;
|
||||
#
|
||||
# # Define your default ports
|
||||
# definitions = {
|
||||
# nginx = 80;
|
||||
# grafana = 3000;
|
||||
# prometheus = 9090;
|
||||
# homepage = 8080;
|
||||
# ssh = 22;
|
||||
# };
|
||||
#
|
||||
# # Define host-specific overrides
|
||||
# hostOverrides = {
|
||||
# laptop = {
|
||||
# nginx = 8080; # Use non-privileged port on laptop
|
||||
# ssh = 2222;
|
||||
# };
|
||||
# server = {
|
||||
# homepage = 3001;
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# # Optionally set the current hostname for automatic port resolution
|
||||
# currentHost = config.networking.hostName;
|
||||
# };
|
||||
#
|
||||
# # Use ports in your configuration:
|
||||
# services.nginx.defaultHTTPListenPort = config.m3ta.ports.get "nginx";
|
||||
# services.grafana.settings.server.http_port = config.m3ta.ports.get "grafana";
|
||||
#
|
||||
# # Or access all ports for the current host:
|
||||
# environment.etc."my-ports.json".text = builtins.toJSON config.m3ta.ports.all;
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.m3ta.ports;
|
||||
|
||||
portsLib = import ../../lib/ports.nix {inherit lib;};
|
||||
|
||||
portHelpers =
|
||||
if cfg.enable
|
||||
then
|
||||
portsLib.mkPortHelpers {
|
||||
ports = cfg.definitions;
|
||||
hostPorts = cfg.hostOverrides;
|
||||
}
|
||||
else null;
|
||||
in {
|
||||
options.m3ta.ports = {
|
||||
enable = mkEnableOption "centralized port management";
|
||||
|
||||
definitions = mkOption {
|
||||
type = types.attrsOf types.port;
|
||||
default = {};
|
||||
description = "Default port definitions for services.";
|
||||
};
|
||||
|
||||
hostOverrides = mkOption {
|
||||
type = types.attrsOf (types.attrsOf types.port);
|
||||
default = {};
|
||||
description = "Host-specific port overrides.";
|
||||
};
|
||||
|
||||
currentHost = mkOption {
|
||||
type = types.str;
|
||||
default = config.networking.hostName;
|
||||
description = "Hostname to use for port resolution.";
|
||||
};
|
||||
|
||||
# Internal computed options
|
||||
get = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
getForHost = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
all = mkOption {
|
||||
type = types.attrsOf types.port;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
allForHost = mkOption {
|
||||
type = types.raw;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
type = types.listOf types.str;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
m3ta.ports.get = service: portHelpers.getPort service cfg.currentHost;
|
||||
m3ta.ports.getForHost = host: service: portHelpers.getPort service host;
|
||||
m3ta.ports.all = portHelpers.getHostPorts cfg.currentHost;
|
||||
m3ta.ports.allForHost = portHelpers.getHostPorts;
|
||||
m3ta.ports.services = portHelpers.listServices;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{inputs, ...}: {
|
||||
# This one brings our custom packages from the 'pkgs' directory
|
||||
additions = final: prev:
|
||||
(import ../pkgs {pkgs = final;});
|
||||
|
||||
# This one contains whatever you want to overlay
|
||||
# You can change versions, add patches, set compilation flags, anything really.
|
||||
# https://nixos.wiki/wiki/Overlays
|
||||
modifications = final: prev:
|
||||
# Import all package modifications from mods directory
|
||||
(import ./mods/default.nix {inherit prev;})
|
||||
// {
|
||||
# Direct configuration overrides
|
||||
brave = prev.brave.override {
|
||||
commandLineArgs = "--password-store=gnome-libsecret";
|
||||
};
|
||||
};
|
||||
|
||||
master-packages = final: _prev: {
|
||||
master = import inputs.nixpkgs-master {
|
||||
system = final.stdenv.hostPlatform.system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{prev}:
|
||||
prev.beads.overrideAttrs (oldAttrs: rec {
|
||||
version = "0.47.1";
|
||||
|
||||
src = prev.fetchFromGitHub {
|
||||
owner = "steveyegge";
|
||||
repo = "beads";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-DwIR/r1TJnpVd/CT1E2OTkAjU7k9/KHbcVwg5zziFVg=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-pY5m5ODRgqghyELRwwxOr+xlW41gtJWLXaW53GlLaFw=";
|
||||
|
||||
# Tests require git worktree operations that fail in Nix sandbox
|
||||
doCheck = false;
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
{prev}: {
|
||||
# Package modifications
|
||||
# This overlay contains package overrides and modifications
|
||||
|
||||
# Add more modifications here as needed
|
||||
# example-package = prev.example-package.override { ... };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{prev}:
|
||||
prev.n8n.overrideAttrs (oldAttrs: rec {
|
||||
version = "2.4.1";
|
||||
|
||||
src = prev.fetchFromGitHub {
|
||||
owner = "n8n-io";
|
||||
repo = "n8n";
|
||||
rev = "n8n@${version}";
|
||||
hash = "sha256-EQP9ZI8kt30SUYE1+/UUpxQXpavzKqDu8qE24zsNifg=";
|
||||
};
|
||||
|
||||
pnpmDeps = prev.pnpm_10.fetchDeps {
|
||||
pname = oldAttrs.pname;
|
||||
inherit version src;
|
||||
fetcherVersion = 1;
|
||||
hash = "sha256-Q30IuFEQD3896Hg0HCLd38YE2i8fJn74JY0o95LKJis=";
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
# pkgs/ AGENTS.md
|
||||
|
||||
## OVERVIEW
|
||||
Custom package registry using `callPackage` pattern for flake-wide availability.
|
||||
|
||||
## STRUCTURE
|
||||
- `default.nix`: Central registry (entry point for overlays)
|
||||
- `code2prompt/`: Rust package
|
||||
- `hyprpaper-random/`: Bash script
|
||||
- `launch-webapp/`: Webapp wrapper
|
||||
- `mem0/`: Python package + custom `server.py`
|
||||
- `msty-studio/`: AppImage wrapper
|
||||
- `pomodoro-timer/`: Timer utility
|
||||
- `tuxedo-backlight/`: Hardware control
|
||||
- `zellij-ps/`: Gitea-hosted package
|
||||
|
||||
## WHERE TO LOOK
|
||||
- **Register new pkg**: Add entry to `pkgs/default.nix` attribute set
|
||||
- **Modify pkg**: Edit `pkgs/<name>/default.nix` (version, hash, deps)
|
||||
- **Check visibility**: `nix flake show` (uses `pkgs/default.nix` via `overlays/default.nix`)
|
||||
- **Add scripts**: Place alongside `default.nix` in package folder (e.g., `mem0/server.py`)
|
||||
|
||||
## CONVENTIONS
|
||||
- **CallPackage**: Always use `pkgs.callPackage ./dir {}` in registry
|
||||
- **Dir == Attr**: Package directory name MUST match its registry attribute
|
||||
- **Path literals**: Reference local assets using `./file` within derivations
|
||||
- **Self-contained**: Keep all package-specific files in their own directory
|
||||
|
||||
## ANTI-PATTERNS
|
||||
- **Orphaned dirs**: Creating `pkgs/new-pkg/` without updating `pkgs/default.nix`
|
||||
- **Direct flake imports**: Importing packages in `flake.nix` instead of through the registry
|
||||
- **Implicit deps**: Not declaring dependencies in the package function arguments
|
||||
- **Non-derivations**: Placing NixOS/HM modules here (use `modules/` instead)
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
lib,
|
||||
fetchFromGitHub,
|
||||
nix-update-script,
|
||||
rustPlatform,
|
||||
pkg-config,
|
||||
perl,
|
||||
openssl,
|
||||
}:
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "code2prompt";
|
||||
version = "4.2.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mufeedvh";
|
||||
repo = "code2prompt";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-Gh8SsSTZW7QlyyC3SWJ5pOK2x85/GT7+LPJn2Jeczpc=";
|
||||
};
|
||||
|
||||
cargoLock = {
|
||||
lockFile = src + "/Cargo.lock";
|
||||
};
|
||||
|
||||
buildAndTestSubdir = "crates/code2prompt";
|
||||
|
||||
nativeBuildInputs = [pkg-config perl];
|
||||
|
||||
buildInputs = [openssl];
|
||||
|
||||
passthru.updateScript = nix-update-script {};
|
||||
|
||||
meta = with lib; {
|
||||
description = "A CLI tool that converts your codebase into a single LLM prompt with a source tree, prompt templating, and token counting";
|
||||
homepage = "https://github.com/mufeedvh/code2prompt";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "code2prompt";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}: let
|
||||
# Used only for flake input pass-throughs (basecamp, openspec, opencode-desktop)
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
in {
|
||||
# Custom packages registry
|
||||
# Each package is defined in its own directory under pkgs/
|
||||
sidecar = pkgs.callPackage ./sidecar {};
|
||||
td = pkgs.callPackage ./td {};
|
||||
code2prompt = pkgs.callPackage ./code2prompt {};
|
||||
eigent = pkgs.callPackage ./eigent {};
|
||||
hyprpaper-random = pkgs.callPackage ./hyprpaper-random {};
|
||||
launch-webapp = pkgs.callPackage ./launch-webapp {};
|
||||
mem0 = pkgs.callPackage ./mem0 {};
|
||||
msty-studio = pkgs.callPackage ./msty-studio {};
|
||||
n8n = pkgs.callPackage ./n8n {};
|
||||
pomodoro-timer = pkgs.callPackage ./pomodoro-timer {};
|
||||
rofi-project-opener = pkgs.callPackage ./rofi-project-opener {};
|
||||
stt-ptt = pkgs.callPackage ./stt-ptt {};
|
||||
tuxedo-backlight = pkgs.callPackage ./tuxedo-backlight {};
|
||||
kestractl = pkgs.callPackage ./kestractl {};
|
||||
openshell = pkgs.callPackage ./openshell {};
|
||||
zellij-ps = pkgs.callPackage ./zellij-ps {};
|
||||
vibetyper = pkgs.callPackage ./vibetyper {};
|
||||
|
||||
# Imported from flake inputs (pass-through, no modifications)
|
||||
basecamp = inputs.basecamp.packages.${system}.default;
|
||||
openspec = inputs.openspec.packages.${system}.default;
|
||||
|
||||
# Imported from flake inputs (with local modifications)
|
||||
opencode-desktop = pkgs.callPackage ./opencode-desktop {inherit inputs;};
|
||||
# opencode-desktop = inputs.opencode.packages.${pkgs.system}.desktop;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
appimageTools,
|
||||
fetchurl,
|
||||
lib,
|
||||
nodejs,
|
||||
uv,
|
||||
python3,
|
||||
nix-update-script,
|
||||
}: let
|
||||
pname = "eigent";
|
||||
version = "0.0.90";
|
||||
src = fetchurl {
|
||||
url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage";
|
||||
hash = "sha256-mwCBx+D6mgGqQa8bDuUpo3h49EwFVkwasJwaYc6aXFE=";
|
||||
};
|
||||
appimageContents = appimageTools.extractType2 {inherit pname version src;};
|
||||
in
|
||||
appimageTools.wrapType2 {
|
||||
inherit pname version src;
|
||||
|
||||
extraPkgs = _: [
|
||||
nodejs
|
||||
uv
|
||||
python3
|
||||
];
|
||||
|
||||
# Runs before bubblewrap launches — sets up writable state for the sandbox.
|
||||
extraPreBwrapCmds = ''
|
||||
# eigent writes to multiple dirs under resources/ at runtime:
|
||||
# prebuilt/ → pyvenv.cfg, .terminal_venv_fixed sentinel
|
||||
# backend/ → creates runtime/ dir for temporary state
|
||||
# Nix store is read-only → EROFS. Copy to writable location on first
|
||||
# launch (or when the package version changes).
|
||||
DATA_DIR="$HOME/.local/share/${pname}"
|
||||
mkdir -p "$DATA_DIR"
|
||||
for subdir in prebuilt backend; do
|
||||
SRC="${appimageContents}/resources/$subdir"
|
||||
DST="$DATA_DIR/$subdir"
|
||||
if [ ! -f "$DST/.nix-src" ] || [ "$(cat "$DST/.nix-src")" != "${appimageContents}" ]; then
|
||||
rm -rf "$DST"
|
||||
cp -r "$SRC" "$DST"
|
||||
chmod -R u+w "$DST"
|
||||
echo "${appimageContents}" > "$DST/.nix-src"
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
# Bind-mount writable copies over the read-only store paths so the app
|
||||
# sees its files at the expected locations but can write to them.
|
||||
extraBwrapArgs = [
|
||||
"--bind $HOME/.local/share/${pname}/prebuilt ${appimageContents}/resources/prebuilt"
|
||||
"--bind $HOME/.local/share/${pname}/backend ${appimageContents}/resources/backend"
|
||||
];
|
||||
|
||||
extraInstallCommands = ''
|
||||
install -m 444 -D ${appimageContents}/eigent.desktop -t $out/share/applications
|
||||
substituteInPlace $out/share/applications/eigent.desktop \
|
||||
--replace-fail 'Exec=AppRun --no-sandbox %U' 'Exec=${pname} %U'
|
||||
install -m 444 -D ${appimageContents}/eigent.png \
|
||||
$out/share/icons/hicolor/256x256/apps/eigent.png
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
updateScript = nix-update-script {};
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Open source AI cowork desktop app — local alternative to Claude Cowork";
|
||||
homepage = "https://github.com/eigent-ai/eigent";
|
||||
license = lib.licenses.asl20;
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "eigent";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
writeShellScriptBin,
|
||||
fd,
|
||||
hyprland,
|
||||
coreutils,
|
||||
gawk,
|
||||
}: let
|
||||
script = writeShellScriptBin "hyprpaper-random" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Directory (override with WALLPAPER_DIR)
|
||||
DIR="''${WALLPAPER_DIR:-''${XDG_CONFIG_HOME:-$HOME/.config}/hypr/wallpapers}"
|
||||
|
||||
HYPRCTL="${hyprland}/bin/hyprctl"
|
||||
FD="${fd}/bin/fd"
|
||||
SHUF="${coreutils}/bin/shuf"
|
||||
TR="${coreutils}/bin/tr"
|
||||
AWK="${gawk}/bin/awk"
|
||||
|
||||
# Pick one random image (null-safe)
|
||||
WALLPAPER="$(
|
||||
"$FD" . "$DIR" -t f -e jpg -e jpeg -e png -e webp -e avif -0 --follow --hidden \
|
||||
| "$SHUF" -z -n1 \
|
||||
| "$TR" -d '\0'
|
||||
)"
|
||||
|
||||
if [[ -z "''${WALLPAPER:-}" ]]; then
|
||||
echo "No wallpapers found in: $DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Preload so hyprpaper can use it
|
||||
"$HYPRCTL" hyprpaper preload "$WALLPAPER" >/dev/null 2>&1 || true
|
||||
|
||||
# Apply to all monitors
|
||||
"$HYPRCTL" monitors \
|
||||
| "$AWK" '/^Monitor /{print $2}' \
|
||||
| while IFS= read -r mon; do
|
||||
[ -n "$mon" ] && "$HYPRCTL" hyprpaper wallpaper "$mon,$WALLPAPER"
|
||||
done
|
||||
|
||||
exit 0
|
||||
'';
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "hyprpaper-random";
|
||||
version = "0.1.1";
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
buildInputs = [
|
||||
fd
|
||||
hyprland
|
||||
coreutils
|
||||
gawk
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p "$out/bin"
|
||||
ln -s ${script}/bin/hyprpaper-random "$out/bin/hyprpaper-random"
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Minimal random wallpaper setter for Hyprpaper";
|
||||
license = lib.licenses.mit;
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "hyprpaper-random";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchurl,
|
||||
autoPatchelfHook,
|
||||
}: let
|
||||
sources = lib.importJSON ./sources.json;
|
||||
source = sources.sources.${stdenv.hostPlatform.system};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "kestractl";
|
||||
version = sources.version;
|
||||
|
||||
src = fetchurl {
|
||||
inherit (source) url hash;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [autoPatchelfHook];
|
||||
|
||||
unpackPhase = ''
|
||||
tar -xzf $src
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
install -Dm755 kestractl $out/bin/kestractl
|
||||
'';
|
||||
|
||||
passthru.updateScript = ./update.sh;
|
||||
|
||||
meta = with lib; {
|
||||
description = "CLI for the Kestra workflow orchestration platform";
|
||||
homepage = "https://github.com/kestra-io/kestractl";
|
||||
license = licenses.asl20;
|
||||
platforms = attrNames sources.sources;
|
||||
mainProgram = "kestractl";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "1.2.2",
|
||||
"sources": {
|
||||
"aarch64-linux": {
|
||||
"url": "https://github.com/kestra-io/kestractl/releases/download/1.2.2/kestractl_1.2.2_linux_arm64.tar.gz",
|
||||
"hash": "sha256-sidFsCZPnJ07PM5QayPBqaqlBBJTLEdecfd0AWnL7Yo="
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"url": "https://github.com/kestra-io/kestractl/releases/download/1.2.2/kestractl_1.2.2_linux_amd64.tar.gz",
|
||||
"hash": "sha256-0C2naN2ougBJSY2z2m6eORnLkLen87HD+a+gvtrUvdw="
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell --pure -i bash -p bash curl jq nix cacert git
|
||||
set -euo pipefail
|
||||
|
||||
# Update kestractl sources.json with the latest release from GitHub.
|
||||
# Usage: ./update.sh (or via nix-update --update-script)
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SOURCES_FILE="$SCRIPT_DIR/sources.json"
|
||||
|
||||
# Map Nix system -> GitHub release asset name fragment
|
||||
declare -A SYSTEMS=(
|
||||
["x86_64-linux"]="linux_amd64"
|
||||
["aarch64-linux"]="linux_arm64"
|
||||
)
|
||||
|
||||
echo "Fetching latest kestractl release..."
|
||||
LATEST=$(curl -fsSL "https://api.github.com/repos/kestra-io/kestractl/releases/latest")
|
||||
VERSION=$(echo "$LATEST" | jq -r '.tag_name')
|
||||
echo "Latest version: $VERSION"
|
||||
|
||||
CURRENT_VERSION=$(jq -r '.version' "$SOURCES_FILE")
|
||||
if [[ "$VERSION" == "$CURRENT_VERSION" ]]; then
|
||||
echo "Already at latest version $VERSION, nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
NEW_SOURCES="{}"
|
||||
|
||||
for NIX_SYSTEM in "${!SYSTEMS[@]}"; do
|
||||
ASSET_FRAG="${SYSTEMS[$NIX_SYSTEM]}"
|
||||
URL="https://github.com/kestra-io/kestractl/releases/download/${VERSION}/kestractl_${VERSION}_${ASSET_FRAG}.tar.gz"
|
||||
|
||||
echo "Fetching hash for $NIX_SYSTEM ($URL)..."
|
||||
HASH=$(nix-prefetch-url --type sha256 "$URL" 2>/dev/null)
|
||||
SRI=$(nix hash to-sri --type sha256 "$HASH")
|
||||
|
||||
NEW_SOURCES=$(echo "$NEW_SOURCES" | jq \
|
||||
--arg sys "$NIX_SYSTEM" \
|
||||
--arg url "$URL" \
|
||||
--arg hash "$SRI" \
|
||||
'. + {($sys): {url: $url, hash: $hash}}')
|
||||
done
|
||||
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--argjson sources "$NEW_SOURCES" \
|
||||
'{"version": $version, "sources": $sources}' \
|
||||
> "$SOURCES_FILE"
|
||||
|
||||
echo "Updated $SOURCES_FILE to $VERSION"
|
||||
|
||||
# Commit when running in CI or via nix-update
|
||||
if [[ -d "$SCRIPT_DIR/../../.git" ]] || git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
NIXPKGS_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
|
||||
if [[ -n "$NIXPKGS_ROOT" && -n "$(git -C "$NIXPKGS_ROOT" status --porcelain "$SOURCES_FILE")" ]]; then
|
||||
git -C "$NIXPKGS_ROOT" add "$SOURCES_FILE"
|
||||
git -C "$NIXPKGS_ROOT" commit -m "kestractl: ${CURRENT_VERSION} -> ${VERSION}"
|
||||
echo "Committed update to git"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
writeShellScriptBin,
|
||||
}: let
|
||||
launcher = writeShellScriptBin "launch-webapp" ''
|
||||
#!/usr/bin/env bash
|
||||
|
||||
browser=$(xdg-settings get default-web-browser)
|
||||
|
||||
case "$browser" in
|
||||
google-chrome*) browser_bin="google-chrome" ;;
|
||||
brave-browser*) browser_bin="brave-browser" ;;
|
||||
microsoft-edge*) browser_bin="microsoft-edge" ;;
|
||||
opera*) browser_bin="opera" ;;
|
||||
vivaldi*) browser_bin="vivaldi" ;;
|
||||
*) browser_bin="chromium" ;;
|
||||
esac
|
||||
|
||||
exec_cmd="/etc/profiles/per-user/$USER/bin/$browser_bin"
|
||||
exec setsid uwsm app -- "$exec_cmd" --app="$1" ''${@:2}
|
||||
'';
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "launch-webapp";
|
||||
version = "0.1.0";
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
ln -s ${launcher}/bin/launch-webapp $out/bin/launch-webapp
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Launches a web app using your default browser in app mode.";
|
||||
license = lib.licenses.mit;
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "launch-webapp";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
lib,
|
||||
nix-update-script,
|
||||
python3,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
python3.pkgs.buildPythonPackage rec {
|
||||
pname = "mem0ai";
|
||||
version = "1.0.9";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mem0ai";
|
||||
repo = "mem0";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-tcWH5VbjIBSHinfjirxbUhxqgU0xOUlcHTQHraMuALg=";
|
||||
};
|
||||
|
||||
# Relax Python dependency version constraints
|
||||
# mem0 has strict version pins that may not match nixpkgs versions
|
||||
pythonRelaxDeps = true;
|
||||
|
||||
build-system = with python3.pkgs; [
|
||||
hatchling
|
||||
];
|
||||
|
||||
dependencies = with python3.pkgs; [
|
||||
litellm
|
||||
qdrant-client
|
||||
pydantic
|
||||
openai
|
||||
posthog
|
||||
pytz
|
||||
sqlalchemy
|
||||
protobuf
|
||||
uvicorn
|
||||
fastapi
|
||||
];
|
||||
|
||||
optional-dependencies = with python3.pkgs; {
|
||||
graph = [
|
||||
# Note: some graph dependencies may not be available in nixpkgs
|
||||
# neo4j is available, others will need to be packaged separately
|
||||
];
|
||||
vector_stores = [
|
||||
# chromadb # available in nixpkgs
|
||||
# pinecone-client # may need packaging
|
||||
# weaviate-client # may need packaging
|
||||
# faiss # available as faiss-cpu
|
||||
psycopg
|
||||
pymongo
|
||||
pymysql
|
||||
redis
|
||||
elasticsearch
|
||||
];
|
||||
llms = [
|
||||
groq
|
||||
openai
|
||||
# together # may need packaging
|
||||
# litellm # may need packaging
|
||||
# ollama # may need packaging
|
||||
# google-generativeai # may need packaging
|
||||
];
|
||||
extras = [
|
||||
boto3
|
||||
# langchain-community # may need packaging
|
||||
# sentence-transformers # may need packaging
|
||||
elasticsearch
|
||||
# fastembed # may need packaging
|
||||
];
|
||||
};
|
||||
|
||||
# Skip tests for now since they require additional test dependencies
|
||||
doCheck = false;
|
||||
|
||||
# Disable imports check because mem0 tries to create directories at import time
|
||||
# which fails in the Nix sandbox (/homeless-shelter)
|
||||
pythonImportsCheck = [];
|
||||
|
||||
postInstall = ''
|
||||
install -Dm755 ${./server.py} $out/bin/mem0-server
|
||||
'';
|
||||
|
||||
passthru.updateScript = nix-update-script {};
|
||||
|
||||
meta = with lib; {
|
||||
description = "Long-term memory layer for AI agents with REST API support";
|
||||
longDescription = ''
|
||||
Mem0 provides a sophisticated memory layer for AI applications, offering:
|
||||
- Memory management for AI agents (add, search, update, delete)
|
||||
- REST API server for easy integration
|
||||
- Support for multiple vector storage backends (Qdrant, Chroma, etc.)
|
||||
- Graph memory capabilities
|
||||
- Multi-modal support
|
||||
- Configurable LLM and embedding models
|
||||
'';
|
||||
homepage = "https://github.com/mem0ai/mem0";
|
||||
changelog = "https://github.com/mem0ai/mem0/releases/tag/v${version}";
|
||||
license = licenses.asl20;
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Mem0 REST API Server
|
||||
A FastAPI-based REST server for mem0 memory operations.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mem0 import Memory
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
|
||||
|
||||
# Configuration from environment variables
|
||||
def get_config_from_env() -> Dict[str, Any]:
|
||||
"""Build mem0 configuration from environment variables."""
|
||||
config = {"version": "v1.1"}
|
||||
|
||||
# Vector store configuration
|
||||
vector_provider = os.environ.get("MEM0_VECTOR_PROVIDER", "qdrant")
|
||||
config["vector_store"] = {"provider": vector_provider}
|
||||
|
||||
if vector_provider == "qdrant":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("QDRANT_HOST", "localhost"),
|
||||
"port": int(os.environ.get("QDRANT_PORT", "6333")),
|
||||
"collection_name": os.environ.get("QDRANT_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
elif vector_provider == "pgvector":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("POSTGRES_HOST", "localhost"),
|
||||
"port": int(os.environ.get("POSTGRES_PORT", "5432")),
|
||||
"dbname": os.environ.get("POSTGRES_DB", "postgres"),
|
||||
"user": os.environ.get("POSTGRES_USER", "postgres"),
|
||||
"password": os.environ.get("POSTGRES_PASSWORD", "postgres"),
|
||||
"collection_name": os.environ.get("POSTGRES_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
elif vector_provider == "chroma":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("CHROMA_HOST", "localhost"),
|
||||
"port": int(os.environ.get("CHROMA_PORT", "8000")),
|
||||
"collection_name": os.environ.get("CHROMA_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
|
||||
# LLM configuration
|
||||
llm_provider = os.environ.get("MEM0_LLM_PROVIDER", "openai")
|
||||
config["llm"] = {
|
||||
"provider": llm_provider,
|
||||
"config": {
|
||||
"model": os.environ.get("MEM0_LLM_MODEL", "gpt-4o-mini"),
|
||||
}
|
||||
}
|
||||
|
||||
# Temperature: only include if set (null means use provider default)
|
||||
temperature = os.environ.get("MEM0_LLM_TEMPERATURE")
|
||||
if temperature is not None:
|
||||
config["llm"]["config"]["temperature"] = float(temperature)
|
||||
|
||||
# Extra config: merge JSON env var if provided
|
||||
extra_config_json = os.environ.get("MEM0_LLM_EXTRA_CONFIG")
|
||||
if extra_config_json:
|
||||
import json
|
||||
try:
|
||||
extra_config = json.loads(extra_config_json)
|
||||
config["llm"]["config"].update(extra_config)
|
||||
except json.JSONDecodeError:
|
||||
logging.warning(f"Failed to parse MEM0_LLM_EXTRA_CONFIG: {extra_config_json}")
|
||||
|
||||
# Add API key if available
|
||||
if llm_provider == "openai":
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if api_key:
|
||||
config["llm"]["config"]["api_key"] = api_key
|
||||
|
||||
# Embedder configuration
|
||||
embedder_provider = os.environ.get("MEM0_EMBEDDER_PROVIDER", "openai")
|
||||
config["embedder"] = {
|
||||
"provider": embedder_provider,
|
||||
}
|
||||
|
||||
# Embedder model: only include if provider is set
|
||||
if embedder_provider:
|
||||
embedder_config = {}
|
||||
embedder_model = os.environ.get("MEM0_EMBEDDER_MODEL")
|
||||
if embedder_model:
|
||||
embedder_config["model"] = embedder_model
|
||||
config["embedder"]["config"] = embedder_config
|
||||
|
||||
if embedder_provider == "openai":
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if api_key:
|
||||
config["embedder"]["config"]["api_key"] = api_key
|
||||
|
||||
# History DB path
|
||||
history_db_path = os.environ.get("MEM0_HISTORY_DB_PATH", "/var/lib/mem0/history.db")
|
||||
config["history_db_path"] = history_db_path
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Initialize Memory instance
|
||||
try:
|
||||
config = get_config_from_env()
|
||||
logging.info(f"Initializing mem0 with config: {config}")
|
||||
|
||||
# Validate API key is set for OpenAI provider
|
||||
if config.get("llm", {}).get("provider") == "openai":
|
||||
if not config.get("llm", {}).get("config", {}).get("api_key"):
|
||||
logging.error("OPENAI_API_KEY environment variable is required but not set.")
|
||||
logging.error("Please set OPENAI_API_KEY environment variable or configure apiKeyFile in NixOS module.")
|
||||
sys.exit(1)
|
||||
|
||||
MEMORY_INSTANCE = Memory.from_config(config)
|
||||
logging.info("Memory instance initialized successfully")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to initialize Memory: {e}")
|
||||
logging.error("Please check your configuration and ensure all required services are running.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Mem0 REST API",
|
||||
description="A REST API for managing and searching memories for your AI Agents and Apps.",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
|
||||
class Message(BaseModel):
|
||||
role: str = Field(..., description="Role of the message (user or assistant).")
|
||||
content: str = Field(..., description="Message content.")
|
||||
|
||||
|
||||
class MemoryCreate(BaseModel):
|
||||
messages: List[Message] = Field(..., description="List of messages to store.")
|
||||
user_id: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
run_id: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
query: str = Field(..., description="Search query.")
|
||||
user_id: Optional[str] = None
|
||||
run_id: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
@app.get("/", summary="Redirect to documentation", include_in_schema=False)
|
||||
def home():
|
||||
"""Redirect to the OpenAPI documentation."""
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
@app.get("/health", summary="Health check")
|
||||
def health():
|
||||
"""Check if the server is running."""
|
||||
return {"status": "healthy", "service": "mem0-api"}
|
||||
|
||||
|
||||
@app.post("/configure", summary="Configure Mem0")
|
||||
def set_config(config: Dict[str, Any]):
|
||||
"""Set memory configuration."""
|
||||
global MEMORY_INSTANCE
|
||||
MEMORY_INSTANCE = Memory.from_config(config)
|
||||
return {"message": "Configuration set successfully"}
|
||||
|
||||
|
||||
@app.post("/memories", summary="Create memories")
|
||||
def add_memory(memory_create: MemoryCreate):
|
||||
"""Store new memories."""
|
||||
if not any([memory_create.user_id, memory_create.agent_id, memory_create.run_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier (user_id, agent_id, run_id) is required.")
|
||||
|
||||
params = {k: v for k, v in memory_create.model_dump().items() if v is not None and k != "messages"}
|
||||
try:
|
||||
response = MEMORY_INSTANCE.add(messages=[m.model_dump() for m in memory_create.messages], **params)
|
||||
return JSONResponse(content=response)
|
||||
except Exception as e:
|
||||
logging.exception("Error in add_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories", summary="Get memories")
|
||||
def get_all_memories(
|
||||
user_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
):
|
||||
"""Retrieve stored memories."""
|
||||
if not any([user_id, run_id, agent_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
||||
try:
|
||||
params = {
|
||||
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
||||
}
|
||||
return MEMORY_INSTANCE.get_all(**params)
|
||||
except Exception as e:
|
||||
logging.exception("Error in get_all_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories/{memory_id}", summary="Get a memory")
|
||||
def get_memory(memory_id: str):
|
||||
"""Retrieve a specific memory by ID."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.get(memory_id)
|
||||
except Exception as e:
|
||||
logging.exception("Error in get_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/search", summary="Search memories")
|
||||
def search_memories(search_req: SearchRequest):
|
||||
"""Search for memories based on a query."""
|
||||
try:
|
||||
params = {k: v for k, v in search_req.model_dump().items() if v is not None and k != "query"}
|
||||
return MEMORY_INSTANCE.search(query=search_req.query, **params)
|
||||
except Exception as e:
|
||||
logging.exception("Error in search_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.put("/memories/{memory_id}", summary="Update a memory")
|
||||
def update_memory(memory_id: str, updated_memory: Dict[str, Any]):
|
||||
"""Update an existing memory with new content."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory)
|
||||
except Exception as e:
|
||||
logging.exception("Error in update_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories/{memory_id}/history", summary="Get memory history")
|
||||
def memory_history(memory_id: str):
|
||||
"""Retrieve memory history."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.history(memory_id=memory_id)
|
||||
except Exception as e:
|
||||
logging.exception("Error in memory_history:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/memories/{memory_id}", summary="Delete a memory")
|
||||
def delete_memory(memory_id: str):
|
||||
"""Delete a specific memory by ID."""
|
||||
try:
|
||||
MEMORY_INSTANCE.delete(memory_id=memory_id)
|
||||
return {"message": "Memory deleted successfully"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in delete_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/memories", summary="Delete all memories")
|
||||
def delete_all_memories(
|
||||
user_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
):
|
||||
"""Delete all memories for a given identifier."""
|
||||
if not any([user_id, run_id, agent_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
||||
try:
|
||||
params = {
|
||||
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
||||
}
|
||||
MEMORY_INSTANCE.delete_all(**params)
|
||||
return {"message": "All relevant memories deleted"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in delete_all_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/reset", summary="Reset all memories")
|
||||
def reset_memory():
|
||||
"""Completely reset stored memories."""
|
||||
try:
|
||||
MEMORY_INSTANCE.reset()
|
||||
return {"message": "All memories reset"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in reset_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
host = os.environ.get("MEM0_HOST", "127.0.0.1")
|
||||
port = int(os.environ.get("MEM0_PORT", "8000"))
|
||||
workers = int(os.environ.get("MEM0_WORKERS", "1"))
|
||||
log_level = os.environ.get("MEM0_LOG_LEVEL", "info")
|
||||
|
||||
uvicorn.run(app, host=host, port=port, workers=workers, log_level=log_level)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user