Imported from dgitco/velcro (
AGENTS.md). Install upstream withnpx skills add dgitco/velcro. Copyright stays with the author.
velcro for agents
velcro is a macOS (13+) menu bar app and velcro command. It keeps SMB and NFS shares mounted, imports recordings from a USB voice recorder into any folder, and sends files to an inbox folder. Everything is configured with velcro commands or the app's Settings; users never need to edit code.
If the user wants to use velcro
Do these in order. Ask before adding or changing anything, and change things only with velcro commands.
- Install.
curl -fsSL https://velcro.dgit.co/install | shinstalls the menu bar app and the command. For the command only (no app):curl -fsSL https://velcro.dgit.co/install | sh -s -- --cli-only. The command lands in~/.local/bin/velcro; use that path if it isn't onPATHyet. From a clone of this repository,./velcro installdoes the command-only setup. - Shares (only if they have a NAS or file server; skip otherwise,
velcro statusthen just says there are none). List what's mounted now withmount -t smbfsandmount -t nfs, show the user, and add the ones they pick:velcro add smb://user@host/share(spaces as%20). If they also reach the server another way, such as a Tailscale name, append it as a fallback host:velcro add smb://me@192.168.1.10/home nas.tail1234.ts.net. If macOS asks for a password, tell them to tick "Remember this password in my keychain". - Voice recorder (optional, no NAS needed). With the recorder plugged in:
velcro recorder add /Volumes/<its name>(without the path it only works when that is the only external drive mounted). Set these in this order, because importing (and deleting from the recorder) starts as soon asimport.destis set: (a) look inside the recorder; if recordings live in one folder (oftenRECORD),velcro set import.folder <that folder>so nothing else on it (music, settings files) is touched; (b) ask whether to delete recordings from the recorder once copied and verified (the default); if not,velcro set import.delete no; (c) ask where recordings should go (a folder on the Mac such as~/Recordings, iCloud Drive, an external drive, or a share) and runvelcro set import.dest <folder>last. A folder outside/Volumesmust have an existing parent (velcro creates only the last folder). With deleting off, files already imported are skipped by the manifest and the recorder is ejected only when something new was copied. For a share, use the path it's actually mounted at: if macOS mounted it as/Volumes/home-1, a folder under/Volumes/homenever counts as attached and recordings wait on the Mac.velcro importruns it now. - Send to an inbox (optional).
velcro set send.inbox <folder>: a folder on a share, or one Dropbox or Syncthing keeps in sync. The folder's parent must exist: for Dropbox, check where it lives (newer installs use~/Library/CloudStorage/Dropbox, older ones~/Dropbox). If another machine (for example a server they use over SSH) sees that folder at a different path,velcro set map "<path on this Mac> -> <path there>"; the left side may start with~/, and several rules go in one command (velcro set map "<a> -> <b>" "<c> -> <d>") or one at a time withvelcro set --add map …. Thenvelcro send <file>(or the app's Send to Inbox menu) saves the file there and copies its path; nothing is sent automatically. Files velcro sent are deleted aftersend.keepdays (14); other files in the folder are never touched. - Mac mic (optional, needs the app). Only if they use a Linux machine over SSH and want voice input there (Claude Code's
/voice, for one). Check thatssh <host> trueworks without a password prompt, thenvelcro mic on <host>: it installsvelcro-mic-hostin~/.local/binthere, checks PipeWire, and starts lending the microphone. Tell them macOS will ask once to let velcro use the microphone, and that it opens only while something on that machine is recording. If the check says PipeWire is missing, stop and tell them; don't install packages on their server unasked.velcro mic offstops it;velcro mic removedeletes the helper. - Finish with
velcro status,velcro settings, andvelcro mic, and tell the user what's set up.
velcro help lists every command and setting. Logs: velcro logs. Undo: velcro rm <share>, velcro recorder rm <name>, velcro set <key> (back to default), velcro uninstall.
If you're working on this code
velcro(zsh) does all the work;app/(SwiftUI, xcodegen) runs it for the menu bar. The exception is Mac Mic:app/Sources/Mic.swiftholds the SSH session and the microphone itself, andvelcro-mic-host(Python, runs on the Linux side) adds the PipeWire microphone and printsstart/stop. Test the helper on Linux without a Mac by feeding it PCM and recording witharecord. PipeWire's pipe source buffers 8192 frames whatever the rate, which is why the helper runs the FIFO at 96 kHz.app/Tools/velcro-shais a small command that hashes a file with the cache off (F_NOCACHE); the app puts it inContents/MacOSand the installer puts it next to a command-only install. Without it, reading back a copy just written to an SMB share returns cached data. The version isVERSION=invelcro.- Build:
scripts/build-app. Site:scripts/deploy-site --stagepreparessite/public; without--stageit also deploys. - Test with a throwaway
HOME(env -u XDG_CONFIG_HOME HOME=/tmp/x zsh ./velcro …) and a disk image as a fake recorder (hdiutil create -fs MS-DOS …).velcro installanduninstallcall the reallaunchctland remove the real SwiftBar plugin even with a fakeHOME, so put stublaunchctl,defaults,open, andosascriptfirst onPATHwhen testing them. - With
pipe_failon, don'treturnfrom insidecmd | while …; loop over an array instead. - Keep personal hosts, user names, and paths out of the repository; examples use
nas.local,/Volumes/home,/mnt/nas/me.
Development rules (shared, 2026-10-15)
- Squash merge only. The PR title becomes the commit title on main, the PR body its message.
- Update a branch with
git rebase origin/main, thengit push --force-with-lease. Never merge main into a branch. - main is protected. No direct pushes, force pushes or deletion, and no admin bypass merges. Every change goes through a PR.
- PR body: Background · Change · Screens (before/after) · Risk (template). If the UI changes, attach before/after images (video for motion). A UI change without images is not merged. No UI change: "N/A — why".
- Upload images and videos with
shot-upload <file>(public R2 bucketpr-shots; prints the URL and markdown). Where it isn't installed, drag the file into the PR editor. Links are public: use fake data or blur anything personal. - Dependent changes go in a stack with
gh stack(gh stack init→gh stack add→gh stack submit). One thing per layer; split when the non-test diff passes about 400 lines. - Tests cover API contracts, permission boundaries, money/date calculations and migrations only. No tests that pin CSS classes, copy, layout, or jsdom viewports. Tests that need a database: tenant isolation and money only.
- Merge condition: a review pass by an agent other than the one that wrote the PR, plus evidence. The reviewer (e.g. a subagent that doesn't know the writing conversation) checks correctness, requirements and evidence only; trivial findings are not acted on. No waiting for human approval.
