Files
2026-08-01 05:50:05 +01:00

31 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: clipsync
description: "CouchDB-backed clipboard history for the user's Macs — daemon, picker, setup."
metadata:
node_type: memory
type: project
originSessionId: 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`.