This is a prototype for a bidirectional mirror between a local folder and an Are.na channel, and vice-versa. Drop a file into a marked folder and it shows up on the channel or add an Image/Attachment block on Are.na and the file appears in the folder. I have always been curious if something like it might be possible, and now with the release of v3 of the Are.na API, it seemed worth a try.
Requires Python 3.10+.
git clone https://github.com/mncmncmnc/arena-sync.git
cd arena-sync
python3 -m pip install -r requirements.txt
# Personal access token: https://are.na/settings/personal-access-tokens
cp .env.example .env
# edit .env and set ARENA_TOKEN=…
chmod +x arena_sync.py
mkdir -p ~/.local/bin
ln -sf "$(pwd)/arena_sync.py" ~/.local/bin/arena-syncMake sure ~/.local/bin is on your PATH (add export PATH="$HOME/.local/bin:$PATH" to ~/.zshrc if needed, then open a new terminal). Check the install:
arena-sync --helpOptional: set a sync root so channels marked with ** on Are.na can be auto-adopted (any directory works)
arena-sync root ~/arenaBoth sides use the super literal substring ** (two asterisks side by side) in their title or folder name:
| Side | How to opt in |
|---|---|
| Local folder | Folder name contains ** (e.g. the channel i want to sync **) |
| Are.na channel | Channel title contains ** |
All allowlisted files inside a tracked folder sync and individual filenames need no marker. Only direct children of the folder are synced (no nested subdirectories). Dotfiles, symlinks, and non-allowlisted types are skipped. Optional per-folder exclusions: .arena-sync/ignore (gitignore-esque).
Allowlisted extensions: jpg jpeg png gif webp heic heif tif tiff bmp svg avif pdf mp3 mp4 mov m4a wav.
| Action | Effect |
|---|---|
| Add file to disk | Uploaded to Are.na |
| Add block on Are.na (Image / Attachment) | Downloaded to disk |
| Delete file from disk | Connection removed from Are.na |
| Delete block on Are.na | Tombstoned: file kept on disk, never re-uploaded |
| Rename file on disk (same content) | Path updated in state, no transfer |
| Modify file content (same path) | Old block disconnected, new content uploaded |
Asymmetric deletes are intentional. Disk deletion is reversible (Trash, Time Machine, file-sync history), so it propagates destructively. Are.na deletion stays defensive, in that it stops syncing that file rather than removing it locally — a misclick in the web UI shouldn't nuke local files.
To clear a tombstone or error and re-upload: arena-sync relink <file>, then sync. To wipe everything and re-sync fresh: delete <folder>/.arena-sync/state.json and run sync again.
Link, Embed, and Text blocks on Are.na are ignored (they don't round-trip to a local file).
Each tracked folder keeps a sidecar at <folder>/.arena-sync/ (channel.json, state.json, optional ignore). Channels are bound by numeric id. Synced channels get an INFO line linking to this repo: Synced with Are.na Sync.
- One machine. The sidecar lives inside the folder, so any file-sync service (Dropbox, iCloud, etc.) will sync it too. I would recommend running
arena-syncon a single computer as two hosts syncing the same folder can conflict overstate.json. forgetonly unregisters. It removes the folder from~/.config/arena-sync/folders. It does not delete the Are.na channel, the local folder, or the.arena-sync/sidecar.
Sync runs only when you ask. This is the intended day-to-day mode.
arena-sync sync # discover marked channels (if root set) + sync all registered
arena-sync sync ~/arena/"the channel i want to sync **" # one folder only (no discovery)
arena-sync status # dry-run, including would-adopt linesarena-sync init ~/arena/"the channel i want to sync **"Creates a private channel titled the channel i want to sync ** (INFO stamped with a link to this repo), writes .arena-sync/, registers the folder, runs an initial sync. Pass --title only if you want the Are.na title to differ from the folder name (it should still always include **).
Auto (with sync root): rename a channel title to the channel i want to sync **, then arena-sync sync. It lands under the sync root as {root}/the channel i want to sync **/, gets registered, stamped, and synced.
Custom path:
arena-sync adopt 1234567 # lands in <sync-root>/<title>
arena-sync adopt 1234567 ~/arena/"the channel i want to sync **" # explicit folder
arena-sync adopt the-channel-i-want-to-sync # by slugadopt requires ** in the channel title. Folder name must include ** too.
arena-sync root # print sync root
arena-sync root ~/arena # set sync root
arena-sync list
arena-sync forget ~/arena/"the channel i want to sync **"
arena-sync relink ~/arena/"the channel i want to sync **/photo.jpg"A launched timer can run arena-sync sync on a schedule. Skip this unless you want unattended automatic mirroring.
cp com.maxwell.arena-sync.plist ~/Library/LaunchAgents/
# Edit ProgramArguments if your symlink path differs
launchctl load ~/Library/LaunchAgents/com.maxwell.arena-sync.plistRuns every 5 minutes. This has the same one-machine caveat as above — a file-sync service will also sync .arena-sync/state.json.
To stop:
launchctl unload ~/Library/LaunchAgents/com.maxwell.arena-sync.plist