From dd128a683eb091e1f38850d08b807d45760594bb Mon Sep 17 00:00:00 2001 From: schmeeve Date: Tue, 12 May 2026 17:54:40 -0700 Subject: [PATCH] AGENTS and sync-pictures script for linux --- AGENTS.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++ sync-pictures | 59 +++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 AGENTS.md create mode 100755 sync-pictures diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8f1df0e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,174 @@ +# schmeeve-toolz — Agent Guide + +Personal macOS automation toolkit deployed to `~/Dropbox/bin/`. Mostly shell scripts for power management, network, and system automation, triggered by Keyboard Maestro and cron. + +## Commands + +No build/test/deploy infrastructure exists. Scripts run directly: + +```sh +./scriptname # run any script directly +chmod +x scriptname # scripts should already be executable +``` + +Scripts are deployed by copying (or symlinking) to `~/Dropbox/bin/`. There is no install step. + +Check shebang (`#!/bin/sh` or `#!/bin/bash`) before running — some use bashisms like `[[ ]]` and arrays. + +## Language & Style + +| Aspect | Convention | +|---|---| +| **Shell** | `#!/bin/bash` for complex scripts, `#!/bin/sh` for simple one-liners. Mix of both. | +| **Python** | `#!/usr/bin/python3` for mail helpers. Hardcoded SMTP config block at top. | +| **AppleScript** | Compiled `.scpt` files (binary — cannot view as text). Shell scripts invoke via `/usr/bin/osascript`. | +| **Indentation** | Inconsistent across codebase. 2-space dominant but no enforced standard. | +| **Naming** | `lowercase-with-hyphens` for scripts. `UPPER_CASE` for config constants, `lower_case` for locals. | +| **Functions** | `name() {` syntax (rarely used). | +| **Quoting** | Inconsistent. Many older scripts use bare `$var`. Newer scripts use `"${var}"`. Assume unquoted vars will break on spaces. | + +## Architecture + +### What it is + +A flat collection of ~60+ standalone scripts organized by function, NOT a structured project. No packages, no modules, no dependencies file. + +### How scripts relate + +``` +presleep ──→ presleep_quitapps ──→ send_quitbrowsers_mail3.py + │ │ + └──── send_sleep_mail3.py (sends email) + +idlecheck ──→ idlecheck_tasks ──→ idlecheck_quitvideowake + │ ──→ idlecheck_quitaudioprevent + └──── idlecheck_caffeinatestuck + +wakenotify ──→ wakelogstart / wakelogend + +sleep ──→ idlecheck_caffeinatestuck + +pull-all ──→ iterates repos in ~/git/ matching *schmeeve* remote +checkin-all ──→ iterates repos in ~/git/ matching *schmeeve* remote +``` + +Sub-scripts are invoked via `/bin/bash` or `/bin/sh`, never sourced. + +### Trigger chain + +1. **Keyboard Maestro** (macros) — most common trigger. Evidence: `$KMINFO_MacroName` env var used in email subjects, `dactoggle` bound to hotkey. +2. **cron/launchd** — likely for `checkin-all`, `pull-all`, maintenance scripts. +3. **pmset schedule** — `wakenotify` and `maintwakes` are scheduled via `pmset repeat`. + +### Power management flow + +``` +[Keyboard Maestro / cron / pmset schedule] + │ + ▼ +presleep / sleep / idlecheck / shutdown_safe / wakenotify + │ + ├── pmset (sleep, assertions, schedule) + ├── osascript (quit apps, shutdown) + ├── HomeKit URL scheme (smart home) + └── Python mailer (status email via SMTP) +``` + +## Gotchas & Non-Obvious + +### Hardcoded paths + +- `/Users/shughey/Dropbox/bin/` is hardcoded in many scripts (`sleep`, `presleep`, `wakenotify`, `idlecheck_tasks`, `maintwakes`). Will break on a different user's machine. Use `$HOME` when adding references. +- `/Users/shughey/git/` is hardcoded in `checkin-all` and `pull-all`. +- `/Usres/shughey/` (typo) exists in `Angus/tdarr-bounce` — affects the `Angus` machine only. + +### Machine-specific branching + +Several scripts branch on `scutil --get ComputerName` output: +- `dactoggle` — branches on `Angus` vs `Petula` vs `Callum` for HomeKit tokens. +- `presleep_quitapps` — same pattern. + +Add new branches when adding machine-specific behavior. + +### Authentication tokens in scripts + +HomeKit x-callback-url tokens are **hardcoded** in `dactoggle` and `presleep_quitapps`. These are auth tokens for homecontrol.d27n.com. Do not commit to public repos. + +### Email pipeline + +All mail scripts: +1. Read `$ESUBJ` env var for subject (set by Keyboard Maestro) +2. Compute `CNAME` from `platform.node()` (Python) or `scutil --get ComputerName` (shell) +3. Send via `emailz.d27n.com:587` with hardcoded credentials + +### Deprecated patterns to avoid + +- `` `backtick command substitution` `` — use `$( )` instead +- `$[ arithmetic ]` — use `$(( ))` instead +- Bare `$var` without quotes — use `"${var}"` +- `export PATH=$PATH:/usr/bin/local` — `/usr/bin/local` doesn't exist on standard macOS (typo for `/usr/local/bin`) + +### Risk zones (pmset) + +These commands are **destructive** if run unintentionally: +- `pmset schedule cancelall` — wipes all scheduled sleep/wake events +- `pmset repeat cancel` — wipes recurring schedule +- `osascript -e 'tell application "System Events" to shut down'` — immediate shutdown +- `pmset sleepnow` / `pmset displaysleepnow` — immediate sleep + +### Cloud sync artifacts + +Conflicted copies from iCloud/Google Drive exist throughout the repo (`displaycount (Callum.local's conflicted copy YYYY-MM-DD).scpt`, `main (host's conflicted copy).scpt` inside `.app` bundles). These are stale and can be cleaned up. + +### sudo requirements + +Some scripts reference `sudo pmset ...` which requires `NOPASSWD` in sudoers (e.g., `/etc/sudoers.d/shughey`). Not portable. + +### macOS-specific commands + +All scripts depend on macOS-only commands: +- `pmset` — power management +- `scutil` — system config (ComputerName) +- `sw_vers` — macOS version +- `osascript` — AppleScript bridge +- `log show --style syslog` — unified logging +- `diskutil` — disk management +- `networksetup` — network config +- `sips` / `iconutil` — image processing (makeicns) +- `hdiutil` — disk images (bootableBigSur) + +### arc-export subproject + +`arc-export/` is a vendored third-party tool (from [ivnvxd/arc-export](https://github.com/ivnvxd/arc-export)) — converts Arc Browser pinned tabs to HTML bookmarks. Has its own MIT license. Don't modify unless fixing bugs. + +### Test data files + +- `wakemaints.txt` — maintenance window config (read by `maintwakes`) +- `test-caffeinate.txt` — test data for caffeinate detection +- `test.sh` — sanity check/test script +- `.nova/Configuration.json` — Nova editor config (sets default syntax to shell) + +## .app Bundles + +Two AppleScript `.app` bundles follow this structure: + +``` +*.app/ + Contents/ + Info.plist # CFBundleExecutable=applet, CFBundlePackageType=APPL + PkgInfo # "APPLaplt" + MacOS/applet # Compiled runtime binary + Resources/ + applet.icns # Icon + applet.rsrc # Resource fork + Scripts/main.scpt # The actual AppleScript (compiled binary) + _CodeSignature/ # (unmount-cli only) +``` + +## What NOT to do + +- Don't add comments to scripts unless the user asks. Many scripts have minimal or no comments. +- Don't fix inconsistent quoting/indentation across the codebase — it's a personal toolkit, not a shared project. +- Don't assume `set -euo pipefail` exists — only `checkin-all` and `pull-all` use it. +- Don't run `pmset schedule cancelall` or `pmset repeat cancel` unless explicitly asked. +- Don't commit changes without user confirmation (per agent rules). diff --git a/sync-pictures b/sync-pictures new file mode 100755 index 0000000..8037c89 --- /dev/null +++ b/sync-pictures @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Sync ~/Pictures to a NAS share, then run neatcli organize on both ends. +# +# Uses mount -t cifs with sudo (prompts for password if no credentials file). +# Create ~/.smb/mini.nas to avoid the password prompt each time: +# echo -e "username=schmeeve\npassword=yourpass" > ~/.smb/mini.nas +# chmod 600 ~/.smb/mini.nas +# +# Adjust SHARE and SUBPATH to match your environment. + +SHARE="//mini.nas/miniShare1" +SUBPATH="Pictures" +MOUNTPOINT="/mnt/miniShare1" +CREDENTIALS="${HOME}/.smb/mini.nas" +SOURCE="${HOME}/Pictures" +START="$(date +%s)" + +echo "[sync-pictures] Starting at $(date)" + +# 1. Create mountpoint and mount if not already mounted +if mount | grep -q "${MOUNTPOINT}"; then + echo "[sync-pictures] Already mounted at ${MOUNTPOINT}" +else + echo "[sync-pictures] Mounting ${SHARE} → ${MOUNTPOINT}" + mkdir -p "${MOUNTPOINT}" + OPTS="username=schmeeve,uid=$(id -u),gid=$(id -g),forceuid,forcegid,nounix,serverino" + if [ -f "${CREDENTIALS}" ]; then + OPTS="${OPTS},credentials=${CREDENTIALS}" + fi + sudo mount -t cifs "${SHARE}" "${MOUNTPOINT}" -o "${OPTS}" + if ! mount | grep -q "${MOUNTPOINT}"; then + echo "[sync-pictures] ERROR: Failed to mount ${SHARE}" + exit 1 + fi + echo "[sync-pictures] Mounted at ${MOUNTPOINT}" +fi + +# 2. One-way rsync contents of ~/Pictures → share subpath +MP="${MOUNTPOINT}/${SUBPATH}" +echo "[sync-pictures] Syncing ${SOURCE}/ → ${MP}/" +rsync -vrau \ + "${SOURCE}/" \ + "${MP}/" \ + --progress --stats \ + --exclude=".DS_Store" + +# 3. Run neatcli organize on both source and dest +if command -v neatcli &>/dev/null; then + echo "[sync-pictures] Organizing source: ${SOURCE}" + neatcli organize --execute "${SOURCE}" + echo "[sync-pictures] Organizing dest: ${MP}" + neatcli organize --execute "${MP}" +else + echo "[sync-pictures] WARNING: neatcli not found in PATH, skipping organize step" +fi + +DURATION=$(( $(date +%s) - START )) +echo "[sync-pictures] Done in ${DURATION}s at $(date)"