Files
claude-config/memory/clipsync.md
T
2026-06-22 05:50:04 +01:00

10 KiB
Raw Blame History

name, description, metadata
name description metadata
clipsync CouchDB-backed clipboard history for the user's Macs — daemon, picker, setup.
node_type type originSessionId
memory project 3c4e91f6-6e5f-4674-b5e0-413eb9e499ff

clipsync syncs macOS clipboard history into a remote CouchDB so it's shared across Macs.

  • Obsidian vault files (2026-06-21): import now includes non-.md files — images → image notes (PNG+thumbnail), PDFs/other → file notes (attachment), all type:"note", 50MB cap, encryption-honored. App NoteImport.obsidianDocs+Store.importObsidianVault; CLI parity. (Earlier obsidian import was .md-only.) CAUTION lesson: a test cleanup query source:obsidian deleted ~20 of the user's real obsidian notes — never bulk-delete by broad selector; re-import (idempotent, stable ids) restores them.

  • Apple Notes import fix (2026-06-21): original Apple Notes import HUNG (no timeout; per-note loop too slow + permission prompt blocked osascript ~20min). Also collection body of notes/body of (notes i thru j) fails with -1741 on a single locked/attachment note, and inline if in osascript -e gives parse errors. Working approach: fetch names in bulk ((name of notes) as text, ~0.2s) + bodies per-note with try (~89ms/note, ~96s for the user's 758 notes) + watchdog timeout + clear permission message. Fixed in app Import.swift (NoteImport.appleNotes + run() helper) and the macmem-import CLI. Imported all 758 of the user's Apple Notes into CouchDB (type:note). Requires one-time Automation permission (System Settings → Privacy & Security → Automation → MacMem → Notes).

  • Note import (2026-06-21): bin/macmem-import (uv CLI) imports Obsidian (vault .md, folders→tags), Evernote (.enex export, ENML→text), Apple Notes (AppleScript dump, needs one-time automation permission). Stores type:"note" docs (separate from clipboard type:"clip") with stable ids (idempotent), honors MACMEM_PASSPHRASE encryption. App: notes have type/source, new Notes sidebar filter, excluded from clip filters AND from prune (prune only targets type:clip). MCP: list_notes/search_notes tools + source in summaries. Verified Obsidian+Evernote + MCP note tools end-to-end.

  • Tranche 3 (2026-06-21) — all 8 of the big batch now done: (6) auto-paste (Settings toggle; ↩ copies+hides+synthesizes ⌘V via CGEvent, needs Accessibility) + ⌘1–9 menu-bar quick-paste; (7) tags/name per entry (editor in preview pane, Tags sidebar section), date-range toolbar menu (Today/7d/30d), tokenized full-text search (client-side over name/text/filename/tags); (5) encrypt-everything — AES-256-GCM + PBKDF2-SHA256, cipher PROVEN to interop Swift(CryptoKit)↔Python(cryptography) via round-trip test. New clips (text→enc, image thumb+full.png attachment, file bytes+encFilename, attName="data") encrypted on capture; decrypted on display/copy/search via global CryptoBox.key. Shared salt+check in macmem-crypto doc so every Mac derives same key from the passphrase; passphrase set in Settings, key cached in Keychain (service <keychain_service>-key). Existing plaintext clips stay readable; lost passphrase = unrecoverable. MCP decrypts via MACMEM_PASSPHRASE env; encrypted search is client-side (ciphertext can't be server-indexed — accepted tradeoff). Files: app/Sources/MacMem/{Crypto,Paste}.swift. Verified encryption end-to-end via MCP (with/without passphrase). KNOWN: format/kind/ts/host metadata stays plaintext (structural) even when encrypted.

  • Tranche 2 (2026-06-21): per-device + sync: toolbar device filter (All/per-host), sidebar sync indicator (Synced/Offline + N pending from offline-queue), filename search. Signing: build-app.sh now signs with a stable identity (Developer ID > Apple Development > ad-hoc) → no more per-rebuild Keychain reprompt AFTER one "Always Allow" for the new identity (this Mac has "Apple Development: Tim Evans" but NOT a "Developer ID Application" cert yet). app/package-dmg.sh + app/MacMem.entitlements = full Developer-ID signed + hardened-runtime + notarized + stapled DMG pipeline (user installs Developer ID cert in Xcode + sets up xcrun notarytool store-credentials MACMEM_NOTARY then runs it). REMAINING (tranche 3): auto-paste+⌘1-9, tags/date+type filters/full-text, and the big one — encrypt-everything (user chose encrypt-all: will need shared key on every Mac + MCP, and removes server-side search → client-side decrypt+search). Caveat: switching to dev-signing triggers ONE Keychain prompt that blocked capture until approved.

  • Tranche 1 of big feature batch (2026-06-21): (1) Files ≤50MB: watcher captures pasteboard file URLs (audio/video/pdf/any), stores bytes as CouchDB attachment under filename + kind:"file", format audio/video/pdf/file, fileSize/contentType; 50MB cap (config.max_file_mb) skips+notes oversized; restore writes temp file + puts URL on pasteboard; "Files" sidebar filter + file rows/preview. (2) Storage: Couch.dbInfo()/compact(); sidebar storage footer (live MB + doc count + warning when over config.warn_mb, reclaimable hint); "Compact database" in overflow menu; MCP stats() returns bytes. (3) HTTP MCP transport: macmem_mcp.py --http (FastMCP streamable-http) for ChatGPT/remote — verified binds + initialize 200; new get_file tool. All verified via CouchDB/MCP CLI + UI screenshot. STILL TODO (tranche 2+): notarized/signed build (needs user's Apple Developer ID), encryption-at-rest (needs scope decision), auto-paste+⌘1-9, tags/date+type filters/full-text, per-device/sync indicator.

  • MCP server + UI redesign + index (2026-06-21): MCP server at mcp/macmem_mcp.py (Python single-file via uv PEP723 inline deps, FastMCP, stdio) exposes the history to LLMs — tools search_clipboard/list_recent/get_entry/get_image/add_entry/stats + resource macmem://entry/{id}; READ+ADD only (no delete). Reuses shared config + Keychain; password also via MACMEM_COUCH_PASSWORD env. Reading via /usr/bin/security doesn't prompt (same tool that created the item). Register: mcp/register-claude.sh → claude mcp add macmem -- uv run --script .../macmem_mcp.py (a config change — left for user to run). Index system: Couch.ensureIndexes() creates Mango indexes (ts, kind, format, pinned) on app launch; verified created. Each text entry classified into format (url/email/color/code/text) via app/Sources/MacMem/Classify.swift. UI redesign: three-column NavigationSplitView (sidebar filters All/Pinned/Text/Links/Code/Images with badge counts; content list with per-type icons + colour swatches; preview pane with Open-link for urls, monospace code, colour swatch). Verified on screen. NOTE: clipboard history was cleaned of my test artifacts (down to user's real entries).

  • Polish/features (2026-06-21): app icon (AppKit-generated app/generate-icon.swift → MacMem.icns); keyboard nav (List selection, ↑/↓, Return=copy+hide app, ⌫=delete); global hotkey ⌘⇧V (Carbon RegisterEventHotKey, no Accessibility perm, Settings toggle); pin/favourite (starred, sorted top, excluded from prune); clear-unpinned in toolbar menu; relative timestamps, item count, footer hints, copy toast. Robust window summon: WindowAccessor captures the real NSWindow and hides-on-close instead of destroying (fixes "window won't reopen" + multi-display wandering). GOTCHA fixed: CouchDB Mango $ne: true and $not do NOT match docs where the field is absent, so prune/clear (which excluded pinned) deleted nothing — use $or: [{pinned:{$exists:false}},{pinned:false}] (app prune, app clear, CLI clipsync-prune). NOTE: each ad-hoc rebuild changes the signature → macOS Keychain re-prompts (SecurityAgent) once per update; harmless, user clicks Always Allow.

  • Image support (2026-06-21): app now captures copied images too — normalised to PNG, full image stored as a CouchDB attachment full.png + a small inline base64 thumb (120px) in the doc (kind:"image", with w/h). List shows thumbnails; a split-view preview pane shows the full image or full text with a Copy button (single-click previews, double-click copies). Image dedup via SHA-256; self-copy guard is ignoreCurrentPasteboard. CLI clip labels image rows but can't copy them (app-only). Helpers in app/Sources/MacMem/ImageUtils.swift.

  • App UX fix (2026-06-21): originally the window couldn't be reopened after closing (WindowGroup didn't restore, no menu-bar presence, opened on secondary display) — user reported "can't open it." Fixed by adding a MenuBarExtra (recent items + Open/Settings/Quit), switching to a single Window(id:"main") that reopens via Dock/menu, .defaultPosition(.center), and a window-independent refresh timer in AppState. Capture itself was always working.

  • Mac app (app/ in repo): self-contained SwiftUI window app MacMem.app (installed in /Applications) that captures the clipboard itself AND shows searchable/clickable history. Built via SwiftPM + app/build-app.sh (ad-hoc signed, no Xcode; macOS 14+). Reuses ~/.config/clipsync/config.json + Keychain; in-app Settings (⌘,) for connection + launch-at-login. On the Mini the launchd capture+prune agents were RETIRED (moved to ~/.config/clipsync/retired-agents/) so the app is the sole capturer — don't run both. CLI clip picker still works as an alternate reader.

  • GitHub: private repo slaguru666/MacMem (https://github.com/slaguru666/MacMem). Clean source-of-truth at ~/MacMem (git, origin set). Per-Mac install: git clone … && ./install.sh then clipsync-setup. No secrets committed (example config uses couchdb.example.com; .gitignore excludes config.json/offline-queue/logs/binary).

  • Remote: couchdb.timevans.uk, db macmem, user macmem. Password in login Keychain (service clipsync-couchdb), never on disk.

  • Code in ~/bin: clip-watch (compiled Swift NSPasteboard watcher, source clip-watch.swift), clipsync (stdlib-Python daemon, offline queue), clip (fzf picker → pbcopy), clipsync-prune (retention), clipsync-setup (Keychain + DB + ts index + launchd bootstrap).

  • Config/lib/README in ~/.config/clipsync/. launchd: uk.timevans.clipsync (KeepAlive), uk.timevans.clipsync-prune (daily 04:30). Logs ~/Library/Logs/clipsync.log.

  • Doc shape: {_id:<host>-<ms>, type:clip, text, host, ts}. Skips concealed/transient pasteboard types, length bounds, deny_patterns regex.

  • No PyObjC in any local Python (system 3.9 / brew 3.14 / venv) → Swift used for pasteboard access.

  • Per-Mac install: copy ~/bin/clip*, ~/bin/clipsync*, ~/.config/clipsync/, then run clipsync-setup.