Specfork

@specfork/clipboard-manager

Local desktop clipboard history for text, HTML, images and files, with limits, search, pins, collections, paste, global hotkey, ignore rules for secrets, editing, optional encryption and retention.

desktopproductivity Version 0.1.1 98 requirements Licence CC-BY-4.0 Specfork/clipboard-manager

Clipboard manager

One person keeps a history of what they copy on their own computer. Every copy of text, formatted text, an image or a list of files becomes an item; items can be searched, edited, pinned, sorted into collections and pasted back into the app the person was using. Content that the source app or the person marks as secret is never stored, history can be capped, aged out, cleared or encrypted, and nothing leaves the device. Behaviour derived from CopyQ's open-source code and documentation (GPL-3.0-or-later), written fresh and stack-free. Not affiliated with or endorsed by Lukas Holecek (hluk) or the CopyQ project. The name is used only to credit the source.

Area Prefix Covers
capture.md CAP Clipboard monitoring, stored formats, size limits, duplicates, pause
sensitive.md SEN Concealed marker, ignore rules, blocked texts, masking, screen-capture protection, logs
selection.md SYN Primary selection: storing, merging, synchronising with the clipboard
items.md ITM Item fields, previews, create, edit, notes, tags, pin, delete, move, sort
collections.md COL History and other collections, favourites, moving items between them
search.md SRC Filtering items, matching rules, regex, scope
paste.md PST Activating items, pasting into the previous window, plain text, tray menu, popup
retention.md RET Count limits, age limits, clearing, history kept in memory only, delete all
storage.md STO Local files, saving, encryption at rest, lock, export and import
app.md APP Background instance, tray, hotkeys, window, shortcuts, command interface, settings

Single user, no account, no server, no network. Runs on Windows, macOS and Linux (X11 and Wayland); platform differences are stated as behaviour. Shortcuts are defaults and configurable; Ctrl means Cmd on macOS. Formats are named by MIME type; instants are UTC ISO 8601; setting and field names are snake_case.

This spec is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0): https://creativecommons.org/licenses/by/4.0/legalcode

It describes behaviour of CopyQ (https://github.com/hluk/CopyQ, GPL-3.0-or-later) in new words; no code or text was copied. See README.md front matter derived_from.

App

Status: Draft · Prefix: APP

Purpose

The background instance, tray, global hotkeys, main window, keyboard shortcuts, the minimal command interface and settings.

Requirements

ID Requirement Verified by
APP-1 One background instance per user session. Starting the app again shows the first instance's window and exits with code 0. start twice → one process, window shown
APP-2 Tray icon: primary click toggles the main window, or opens the tray menu when tray_click is menu; the menu has recent items (PST-9), pause/resume (CAP-7), show/hide, clear history, settings and quit. Without a tray area the app relies on hotkeys and says so once. click tray → window toggles
APP-3 Global hotkeys, configurable: show popup (PST-8) Ctrl+Alt+V (macOS Cmd+Option+V); show/hide main window, pause/resume and paste_item_n unset. A hotkey held by another app or the OS is marked not registered; the rest still work. another app holds Ctrl+Alt+V → marked
APP-4 Wayland: hotkeys go through the desktop's global-shortcut service when present (the desktop may ask to confirm); otherwise settings show the toggle and menu commands (APP-7) to bind in the desktop's own settings. Wayland without service → commands shown
APP-5 Main window: the current collection's items, collection bar, filter. It opens on the display with the pointer, keeps size and position where the platform allows, hides on Esc, and hides 500 ms after losing focus when close_on_unfocus is on (default). click elsewhere → hidden after 0.5 s
APP-6 Window shortcuts, configurable, defaults: Enter activate, Shift+Enter plain text, F2 edit, Shift+F2 note, Ctrl+E external editor, F4 formats, Delete remove, Ctrl+N new item, Ctrl+T new collection, Ctrl+W delete collection, Ctrl+Shift+P pin, Ctrl+Up/Down move, Ctrl+, settings, Ctrl+Q quit. Assigning a key already in use is refused with the action that holds it. assign F2 to pin → refused, names edit
APP-7 Command interface: a CLI talks to the running instance over a per-user local channel, never a network port: show, hide, toggle, menu, add <text>…, read [row] [--format <MIME>], select <row> (activate without paste), remove <row>…, count, pause [minutes], resume, clear, each optionally after --collection <name>. Rows are 0-based from the top. add a b, count → 2
APP-8 Command exit codes: 0 done, 1 no running instance, 2 usage error or no such row/collection, 3 locked (STO-6). add goes through SEN-3 rules like a copy. Requests from another OS user are refused. locked, read 0 → exit 3
APP-9 start_at_login (default off) is offered once on first run and adds or removes a per-user login item without administrator rights. accept on first run → starts after next login
APP-10 Settings live in one per-user file; unknown keys are ignored and invalid values fall back to their default, each with a warning, and the rest still load. Changes apply without restart. max_items=-4 → 200 used, warning
APP-11 ui_language (auto = OS language, falls back to English); theme: system|light|dark (system); confirm_quit (default on) asks before quitting because capture stops. quit → confirmation shown

Out of scope

  • Scripting language, plugins, automatic commands triggered by clipboard content, menus of custom actions; only APP-7 commands exist.
  • Self-update and installers.

Decisions

  • A minimal command set (APP-7) replaces the source's full scripting engine, which is out of scope.
  • The show hotkey defaults to Ctrl+Alt+V, avoiding Ctrl+Shift+V, which many apps use to paste as plain text.

Capture

Status: Draft · Prefix: CAP

Purpose

Turning changes of the system clipboard into history items: what is stored, what is skipped, and how often.

Requirements

ID Requirement Verified by
CAP-1 With store_clipboard on (default), every clipboard change made by another app becomes an item at the top of the history collection (COL-1) within 1 s, after the checks of SEN and CAP-3..6. copy "abc" in an editor → item "abc" on top within 1 s
CAP-2 Stored formats: text/plain (as UTF-8), text/html, text/uri-list (files), image/png (platform bitmaps converted to PNG), image/svg+xml, image/gif, plus any MIME type listed in extra_formats (empty). Other formats are dropped; a change with no stored format makes no item. copy only a private app format → no item
CAP-3 Text that is empty or only whitespace, with no other stored format, makes no item while ignore_blank is on (default). copy " " → no item
CAP-4 format_size_limits: ordered {pattern (MIME glob), max_bytes}, first match wins; default one rule * = 100 MiB; max_bytes 0 never stores that format. An over-limit format is dropped and the rest kept; nothing left → no item. rule text/html 0, copy from a web page → item has text only
CAP-5 Duplicate: when every stored format equals an existing unpinned item of the history collection, that item moves to the top and gets a new last_used_at; no item is added. A duplicate of a pinned item adds nothing and moves nothing. copy "a", "b", "a" → 2 items, "a" on top
CAP-6 Changes the app makes itself (activating an item, PST-1) create no item; the activated item moves to the top when move_activated_to_top is on (default). activate 3rd item → it is first, count unchanged
CAP-7 Monitoring can be paused and resumed from the tray, a hotkey and the command interface, for 5, 15 or 60 minutes or until resumed. While paused nothing is captured and the tray icon shows the paused state; a timed pause resumes by itself. pause 5 min, copy → no item; after 5 min, copy → item
CAP-8 Change detection works on every platform within 1 s. On Wayland it needs the compositor's clipboard-access protocol or a desktop extension; without them the app says monitoring is unavailable and stays usable for manual items and paste. Wayland without protocol → notice, Ctrl+N still works
CAP-9 notify_on_copy (default off): a notification on each new item with its first notification_lines lines (0..20, default 3) of text, or image W×H or the file count; never for content handled by SEN. on, copy 2 files → notification "2 files"
CAP-10 Each captured item records source_app and source_window_title only while record_source is on (default off); otherwise both are used for SEN-3 and then discarded. default, copy → item has no source fields

Out of scope

  • Capturing drag-and-drop data or the macOS find pasteboard.
  • Converting between formats beyond bitmap → PNG.

Decisions

  • Recording the source window is off by default (CAP-10): window titles often reveal document names and are not needed to use history.
  • The size limit default of 100 MiB matches the source; the per-format list replaces its environment variable with a setting.

Collections

Status: Draft · Prefix: COL

Purpose

Grouping items into named collections: the history that receives copies, favourites, and any others the person makes.

Requirements

ID Requirement Verified by
COL-1 Collection: {id, name (1..100 chars, trimmed, unique case-insensitive), position, kind: history|regular, max_items (optional, overrides [RET-1](#RET-1))}. Exactly one collection is history (default name "Clipboard"); captures go there. fresh install → one collection "Clipboard"
COL-2 Another collection can be made the history; the previous one becomes regular and keeps its items. make "Work" history → next copy lands in "Work"
COL-3 Ctrl+T creates a collection; rename from its menu; Ctrl+W deletes the current one after a confirmation naming its item count. The history collection cannot be deleted. Ctrl+W on history → refused
COL-4 With collection_tree on (default off), / in names groups collections in a tree: "Work/Snippets" shows under "Work". create "A/B" → tree node A with child B
COL-5 The collection bar is hidden while only one collection exists; with show_counts on (default off) each collection shows its item count. create second collection → bar appears
COL-6 Items move or copy between collections by drag (Ctrl held = copy) or the item menu; formats, note, tags and sensitive travel along, pinned becomes false, and the target's limit applies (RET-1). move pinned item → arrives unpinned
COL-7 Favourites: "add to favourites" copies the item into the collection named by favourites_collection (default "Favourites"), created on first use; adding an equal item again does nothing. add twice → one copy in Favourites
COL-8 Navigation: Ctrl+Tab / Ctrl+Shift+Tab next and previous, Ctrl+1..Ctrl+9 the nth collection, Ctrl+0 the last. Ctrl+2 → second collection shown

Out of scope

  • Collections backed by a folder on disk or a cloud folder.
  • Automatic sorting of new items into collections by rules or scripts.

Decisions

  • Favourites are a plain collection (COL-7), so no separate data model is needed.

Items

Status: Draft · Prefix: ITM

Purpose

What an item holds, how it is shown, and how the person creates, edits, annotates, pins, deletes and orders items.

Requirements

ID Requirement Verified by
ITM-1 Item: {id (UUID), collection_id, position, formats: {MIME type: bytes}, created_at, last_used_at, origin: clipboard|selection|manual|import|command, pinned (false), sensitive (false), note (≤ 10000 chars), tags (≤ 20, each 1..50 chars, unique case-insensitive)}. add 21st tag → refused
ITM-2 Preview: text shows its first preview_lines lines (1..50, default 5) with a marker when cut; HTML shows formatted when rich_text is on (default), else its text; images show a thumbnail within preview_image_max (default 320×240 px) keeping aspect; file lists show names and count; other formats show MIME types and sizes. Note and tags show under the preview. 12-line text → 5 lines + "more" marker
ITM-3 Ctrl+N creates an empty text item at the top of the current collection with origin: manual and opens the editor; saving empty text discards it. Ctrl+N, save empty → no item
ITM-4 F2 edits text in place; saving replaces text/plain and drops text/html if the text changed; Esc discards the edit. Image-only items cannot be edited in place. edit an HTML item's text → item has text only
ITM-5 Ctrl+E opens the item in external_editor (text) or image_editor (images) through a temporary file readable only by the user; each save of that file updates the item; the file is deleted when the editor exits. change text in editor, save → item updated
ITM-6 Shift+F2 edits the note; tags are added or removed from the item menu. Notes and tags are searchable (SRC-1). add tag "work" → shown under item
ITM-7 Pin (Ctrl+Shift+P toggles): a pinned item keeps its row when items are added or removed above or below it, is never removed by limits (RET-2) and cannot be deleted until unpinned; the person can still move it. pin row 3, copy 2 new items → still row 3
ITM-8 Delete removes the selected unpinned items at once; an "undo" offer stays for 10 s, after which the data is purged per RET-7. Selected pinned items are skipped with a notice. delete 2, undo within 10 s → both back
ITM-9 Order: drag, or Ctrl+Up/Ctrl+Down/Ctrl+Home/Ctrl+End moves selected items; Ctrl+Shift+S sorts them by text (case-insensitive, locale-aware), Ctrl+Shift+R reverses them. Multi-select with Shift/Ctrl-click and Ctrl+A. select 3, Ctrl+Shift+R → order reversed
ITM-10 F4 shows each format of the item with its MIME type and size and lets the person view it as text or image, or remove one format; removing the last format deletes the item. remove text/html → item keeps text
ITM-11 sensitive can be set and cleared by the person on any item (SEN-6). flag item → preview masked

Out of scope

  • Rendering arbitrary formats (rich documents, audio, video); they show as format lists.
  • Vim-style editing and syntax highlighting.

Decisions

  • Editing text drops the HTML version (ITM-4) so the two never disagree when pasted.
  • Pin uses Ctrl+Shift+P; the source has no default key for it.

Paste

Status: Draft · Prefix: PST

Purpose

Getting an item back into the app the person is working in: activation, pasting, plain text, several items, the tray menu and the popup.

Requirements

ID Requirement Verified by
PST-1 Activate (Enter, double-click, or single click when activate_on_single_click is on, default off) puts every stored format of the item on the clipboard, updates its last_used_at and moves it per CAP-6. activate HTML item, paste in a rich editor → formatted
PST-2 activate_pastes (default on): after activation the window hides, focus returns to the window active before the app opened, and the paste keystroke is sent. activate_pastes off: copy only; activate_hides (default on) controls hiding. text editor focused, popup, Enter → text inserted
PST-3 Paste keystroke: Ctrl+V (macOS Cmd+V) by default; paste_key_rules: ordered {app or window_title regex, keys} override it, e.g. Ctrl+Shift+V or Shift+Insert for terminals. rule terminal → Ctrl+Shift+V sent there
PST-4 If the previous window no longer exists or cannot take focus, the item is only copied and a notice says so. close target app, activate → copied, notice
PST-5 Wayland: when sending key events to other apps is not permitted, the app copies only and says "copied, paste with Ctrl+V", unless the desktop offers a consented input service. macOS: sending the paste needs the Accessibility permission; without it, copy only and explain how to grant it. macOS without permission → copied, hint shown
PST-6 Shift+Enter activates as plain text: only text/plain is put on the clipboard. Shift+Enter on HTML item → paste is unformatted
PST-7 Several selected items activate as one: their texts joined by item_separator (default newline) in list order, text only. Selected items without text are skipped. select "a","b", Enter → "a\nb"
PST-8 Popup: the show hotkey (APP-3) opens a compact list of the history collection at the pointer (Wayland: centre of the active display), focused on the filter; Enter, a click or digits 1..9 (first nine rows) activate. hotkey, press 2 → second item pasted
PST-9 Tray menu lists the first tray_items (0..99, default 5) items of the history collection with one-line previews (masked when sensitive); choosing one activates it, pasting only when tray_pastes is on (default on). 5 items in tray menu, choose 3rd → pasted
PST-10 Optional hotkeys paste_item_1..paste_item_9 (unset by default) activate and paste that row of the history collection without opening any window. bind paste_item_2, press → 2nd item pasted

Out of scope

  • Typing text key by key into apps that block paste.
  • Templates or placeholders expanded on paste.

Decisions

  • Pasting into the previous window is the default (PST-2), as in the source; platforms that forbid it fall back to copy with a clear hint (PST-5) instead of failing silently.

Retention

Status: Draft · Prefix: RET

Purpose

How much history is kept, for how long, and how the person removes it.

Requirements

ID Requirement Verified by
RET-1 max_items 1..100000 (default 200) per collection, overridable per collection (COL-1). Adding an item to a full collection removes the bottom-most unpinned item. limit 3, add 4th → oldest unpinned gone
RET-2 Pinned items are never removed by limits. When every item is pinned and the collection is full, the new item is not added and a notice says why, once per hour at most. 3 pinned, limit 3, copy → no item, notice
RET-3 Lowering max_items below the current count asks for confirmation naming how many items will be removed, then trims at once. 200 items, set 50 → "150 items will be removed"
RET-4 max_age_days 0..3650 (default 0 = off): unpinned items of the history collection whose last_used_at is older are removed at start and every hour. Other collections are never aged. 30 days, item last used 31 days ago → removed
RET-5 Clear history: removes all unpinned items of the history collection and the search history after confirmation; a checkbox in the same confirmation also removes pinned ones. clear → only pinned remain
RET-6 clear_on_exit (default off): unpinned history items are removed on quit, and at next start if the app did not quit cleanly. on, kill app, start → history empty
RET-7 Removal is real: a removed item's data, including separately stored large formats, is gone from storage at the next save (STO-3); there is no hidden trash. remove image item, inspect data folder → its file gone
RET-8 persist_history (default on): when off, the history collection is held in memory only and never written to disk; other collections are still saved. off, copy, quit, start → history empty, no history file
RET-9 Delete all data: after typing DELETE, every collection, item, ignore rule, blocked hash and search query is removed and the history collection recreated empty; settings stay. delete all → one empty "Clipboard"

Out of scope

  • Size-based quotas for a whole collection (bytes); limits are per format (CAP-4) and per item count.

Decisions

  • Default limit 200 items matches the source; age limits and memory-only history are designed here.
  • @specfork/privacy is not required: it depends on @specfork/auth, which this account-free app does not use; delete-all (RET-9) and export (STO-10) follow the same intent locally.

Selection

Status: Draft · Prefix: SYN

Purpose

Where the OS has a primary selection next to the clipboard, storing selected text and keeping the two in step.

Requirements

ID Requirement Verified by
SYN-1 These rules apply only where a primary selection exists: Linux X11, and Wayland compositors with the primary-selection protocol. Elsewhere the settings are hidden. Windows → no selection settings
SYN-2 store_selection (default off): selection changes also become items, with origin: selection, through the same checks as CAP and SEN. on, select "foo" → item "foo"
SYN-3 Growing selection: when the new selection text starts or ends with the text of the top item and that item came from the selection, the top item is updated instead of adding one. select "he", extend to "hello" → one item "hello"
SYN-4 When the clipboard receives the same text as the top selection item, the clipboard's formats are merged into that item instead of adding one. select "x", then copy "x" → one item with html too
SYN-5 sync_clipboard_to_selection and sync_selection_to_clipboard (both default off) copy the text of one to the other after it changes. Only text formats are copied. clipboard → selection on, copy "a" → middle-click pastes "a"
SYN-6 A sync waits until no Shift, Ctrl or Alt key is held (giving up after 5 s) and at least 50 ms after the change, and is skipped when the source changed again meanwhile or the target already holds the same text. hold Shift while selecting → no sync until released
SYN-7 Content ignored or concealed by SEN is never synchronised. concealed clipboard → selection unchanged
SYN-8 On Wayland without the needed protocols the settings show as unavailable with the reason. protocol missing → options greyed with reason

Out of scope

  • The macOS find pasteboard and other secondary buffers.

Decisions

  • Sync is off in both directions by default; each one silently overwrites data, so the person turns it on knowingly.

Sensitive data

Status: Draft · Prefix: SEN

Purpose

Clipboard contents include passwords, tokens and private text. These rules decide what is never stored, how stored items can be hidden, and what the app never reveals.

Requirements

ID Requirement Verified by
SEN-1 Concealed content is never stored, shown, notified, synchronised or logged: on Linux the password-manager hint format set to secret; on macOS the concealed pasteboard type; on Windows any of the formats that exclude content from clipboard history or monitoring, or set history or cloud upload to 0. password manager copies a password → no item, no notification
SEN-2 store_concealed_from: list of app identifiers whose concealed content may be stored (empty). Adding one needs encryption at rest on (STO-4) and a confirmation; items stored this way are flagged sensitive (SEN-6). There is no global switch. encryption off, add an app → refused
SEN-3 Ignore rules: ordered {name, enabled, app (exact identifier), window_title (regex), text (regex), format (MIME)}; a change matching every set field of an enabled rule is ignored. A rule with no field set is invalid. rule window_title (?i)vault, copy from "My Vault" → no item
SEN-4 Default rule password_windows, enabled: window title matches (?i)passw(or)?d|passwort|mot de passe|contraseña. copy from a window titled "Password Safe" → no item
SEN-5 Blocked texts: "never store this text" on an item deletes it and keeps only a salted SHA-256 of its text; a later copy with the same text is ignored. The text itself is not kept anywhere. Blocked hashes can be listed (by date only) and removed. block "hunter2", copy it again → no item
SEN-6 Items flagged sensitive (by SEN-2 or by the person) show a masked preview (dots plus character count), are matched by search only through note and tags, never appear in notifications, and show masked in the tray menu. flag an item → list shows "•••• (12 chars)"
SEN-7 After a sensitive item is activated, the clipboard is emptied clear_sensitive_after_s seconds later (0..3600, default 30; 0 = never), unless the clipboard changed in between. activate sensitive item, wait 30 s → clipboard empty
SEN-8 Ignored and concealed content leaves no trace: no item, no notification, no log line with its content; the other app's clipboard is left as it is. copy concealed text, paste elsewhere → still pastes
SEN-9 hide_from_screen_capture (default on): the app's windows and menus are excluded from screenshots and screen sharing where the OS supports it (Windows, macOS); elsewhere settings say it has no effect. show_clipboard_in_title (default off) controls whether the current clipboard appears in the window title and tray tooltip. Windows, share screen → app window blank to viewers
SEN-10 Logs contain no item content, window titles or regex texts: only counts, rule names and error reasons. ignored copy by rule "bank" → log says rule "bank" matched, no content

Out of scope

  • Detecting secrets by content heuristics (entropy, card numbers).
  • Per-item encryption separate from STO-4.

Decisions

  • Concealed content can be stored only per app and only with encryption on (SEN-2); the source allows a global override through scripting.
  • Clearing the clipboard after a sensitive paste (SEN-7) is built in; the source offers it as an optional shared script.

Storage

Status: Draft · Prefix: STO

Purpose

Where items live on the device, how they are saved safely, optional encryption at rest with locking, and moving data in and out.

Requirements

ID Requirement Verified by
STO-1 All data stays in the per-user OS data folder, created readable and writable only by that user; the app makes no network requests at all. inspect permissions → owner-only; traffic monitor → zero requests
STO-2 Format data larger than inline_max_bytes (default 4096) is stored in its own file referenced by the item and deleted with it. copy 1 MB image → separate file exists
STO-3 Changes are saved within 2 s; every write goes to a temporary file that replaces the old one only when complete, so a crash loses at most the last 2 s and never corrupts a collection. kill app mid-save → previous state loads
STO-4 encrypt (default off): all collection files, separate format files, search history and blocked hashes are encrypted with authenticated encryption (AES-256-GCM or XChaCha20-Poly1305) under a random 256-bit data key; the data key is wrapped by a key derived from the person's password (Argon2id ≥ 64 MiB, ≥ 3 passes). Settings and ignore rules stay readable. encrypt on, search data folder for a copied word → not found
STO-5 Turning encryption on asks for a password (≥ 8 chars, typed twice), says a forgotten password makes the data unrecoverable, rewrites all data encrypted and deletes the plain files. Turning it off needs the password and rewrites plain. turn on → old plain files gone
STO-6 Locked state (at start with encryption on, or after STO-7): items are not shown, monitoring is paused and nothing is captured, the tray shows locked; entering the password unlocks. locked, copy → no item after unlock
STO-7 lock_after_min 0..1440 (default 0 = never): with encryption on, after that many minutes without interaction with the app, the data key is dropped from memory and the app locks. 5 min idle → password needed
STO-8 use_os_keystore (default off): the data key is kept in the OS credential store so start needs no password; STO-7 still applies. on, restart → unlocked without prompt
STO-9 Changing the password needs the old one and re-wraps only the data key, taking under 1 s at any history size. Five wrong passwords in a row add a delay of 30 s, doubling each further failure up to 1 h; data is never deleted for failures. 6th wrong try → wait 60 s
STO-10 Export: chosen collections or all into one file {format: "clipboard-manager-export", version: 1, exported_at, collections: [{name, kind, items: [{formats (base64), created_at, last_used_at, pinned, note, tags, sensitive}]}]}; sensitive items are left out unless chosen; an optional export password encrypts the whole file (STO-4 scheme). export 3 items → file has 3 items
STO-11 Import of such a file adds its items as new items (new ids) into collections of the same name, created when missing; nothing existing is overwritten; limits (RET-1) apply; a wrong password or another format/newer version → unsupported_export and nothing changes. import twice → items twice, originals untouched

Out of scope

  • Sync between devices, cloud backup, sharing collections.

Decisions

  • Save within 2 s (STO-3); the source batches saves for minutes, which loses recent history on a crash.
  • The OS key store is off by default (STO-8), so encrypted data needs the password after restart; the source turns it on by default.
  • Locking pauses capture (STO-6) rather than queueing plaintext in memory.

From Specfork/clipboard-manager at 3c213d0, licence CC-BY-4.0.