
Clipboard Memory
- 2 installs
- 14 repo stars
- Updated July 11, 2026
- tristanmanchester/clipmem
Helps with ai & agent building tasks during AI-assisted development.
About
clipboard-memory is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- clipboard-memory
- AI & Agent Building
- AI-coding skill
Clipboard Memory by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,956 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tristanmanchester/clipmem --skill clipboard-memoryAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 14 |
| Last updated | July 11, 2026 |
| Repository | tristanmanchester/clipmem ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Recall what the user copied on this Mac before reaching for generic search. clipmem maintains a local, privacy-preserving SQLite archive of every clipboard state macOS emits, and exposes a JSON-first CLI built for agents. The canonical public copy lives at skills/clipboard-memory/; the OpenClaw-native packaging variant lives at extras/openclaw/clipboard-memory/.
Use this skill when
The user asks things like:
- "what was that command I copied?"
- "show me the URL I copied from Safari earlier"
- "find that snippet, path, note, or link I copied yesterday"
- "give me the exact text I copied, not a summary"
- "what did I copy before I restarted?"
- "paste me back that SQL I was looking at"
- "get the PDF I copied last week"
- "show me everything I copied from Xcode today"
Do not use this skill for
- web search or current-events lookups
- searching the repository or local files the user never copied
- content the user typed but never copied to the clipboard
- anything on a non-macOS machine (
clipmemcapturesNSPasteboardonly)
Prerequisites
Before querying, confirm the setup is healthy — otherwise empty results may be a stale watcher, not a true miss:
1. Background capture must be running. clipmem setup is the canonical fix; Homebrew users can also use brew services start clipmem. 2. The binary clipmem must be on PATH with write access to ~/Library/Application Support/clipmem/clipmem.sqlite3. 3. Run `scripts/check-setup.sh` once per session when results look wrong. It exits 0 on a healthy host, 1 if the watcher is stale, 2 if the binary is missing, 3 if clipmem doctor fails. The prose equivalent is in references/setup-check.md.
Command ladder
Always pick the narrowest command that answers the question, and always pass --format json (or --format toon for plain enumeration) so you can parse the response deterministically.
1. `clipmem recall` — best-first ranked answer with alternatives. Start here for almost every request. 2. `clipmem timeline` — chronological capture events (one row per copy), including repeated copies of the same content. Use for "today", "yesterday", "in order", or "every time". 3. `clipmem search` — direct lexical / FTS matching. Use when you need precise substring hits or the user gave you an exact phrase. 4. `clipmem get <snapshot_id>` — nested item/representation detail for a single snapshot already in hand. 5. `clipmem export <snapshot_id> --item <n> --uti <uti> --out <path> [--force]` — raw bytes. Use when the stored content is binary/image/PDF and best_text is empty or partial. Prefer a fresh output path; use --force only to replace an existing regular file.
The full flag reference, JSON envelope, and kind values live in references/commands.md, references/json-schema.md, and references/examples.md.
Output format rule
--format json— single structured object. Use whenever you will parse the response. Stable withinschema_version: 2.--format toon— flat, token-efficient list. Prefer for high-cardinality enumeration (timeline,search,recent,recall) when you only need the top fields. Note:getdoes not supporttoon.--format jsonl— newline-delimited records. Use when streaming many rows into a pipeline.--format md/--format text— human-readable previews only; never parse these.
--json is an alias for --format json on search, recent, timeline, get, capture-once, and doctor.
Which command for which intent
| User intent | First command |
|---|---|
| "what was that thing I copied" (no time cue) | recall "<query>" --format json |
| "what did I copy today / yesterday / in order" | timeline --hours <N> --format json |
| "recent unique things I copied" | recent --hours <N> --format json |
| exact substring or punctuation-heavy query | search --mode literal "<query>" --format json |
| already have a snapshot id | get <id> --format json |
| need raw image / PDF bytes | get <id> then export <id> --item <n> --uti <uti> --out <path> |
recall vs recent vs timeline:
recallranks across the archive and returns a best candidate plus alternatives.recentdeduplicates by snapshot — identical copies collapse into one row.timelineis event-centric — every capture event is its own row, even if the content repeats.
Quick examples
# best-first answer
clipmem recall "that command I copied" --format json --limit 5
# Safari today, token-efficient
clipmem recall --prefer-recent --app safari --hours 24 --format toon
# exact URL yesterday
clipmem recall "url" --has-url --hours 48 --format json
# chronological sweep, paginated
clipmem timeline --hours 24 --limit 25 --format json
clipmem timeline --hours 24 --limit 25 --cursor "<next_cursor>" --format json
# recover an image
clipmem get 42 --format json
clipmem export 42 --item 0 --uti public.png --out ./clipboard.png
clipmem export 42 --item 0 --uti public.png --out ./clipboard.png --forceReading the response
Read these JSON fields first; walk nested items[].representations[] only after a get call:
best_candidate.best_text— the flattened primary text.best_candidate.urls— URL array (empty when none).best_candidate.file_paths— file-URL array.why_selected,best_match_confidence,alternatives(only onrecall).next_cursor,truncated— pagination state.schema_version— pin to2for stability.
Full schema in references/json-schema.md.
Troubleshooting
If recall looks empty or weak, widen --hours, drop source filters, or switch to timeline / search. For setup issues, sandbox PATH problems, or binary-only snapshots, see references/troubleshooting.md.
Exit codes
0 success · 1 uncategorized runtime · 2 invalid args · 3 not found · 4 unsupported format · 5 database error · 6 platform error.
Clipboard Memory — Commands Reference
Full flag and subcommand reference for clipmem. This file is kept byte-identical across the OpenClaw-native and portable skill packages.
---
Decision ladder
Pick the narrowest command that answers the question. Always pass --format json (or --format toon for plain enumeration) when parsing programmatically.
1. clipmem recall "<query>" --format json — best-first ranked answer with alternatives. Start here. 2. clipmem timeline --hours <N> --format json — chronological capture events. Use when the user says "today", "yesterday", "in order", or "every time". 3. clipmem recent --hours <N> --format json — deduplicated recent snapshots. Use for "recent unique things". 4. clipmem search "<query>" --format json — direct lexical / FTS match. Use when you need precise substring hits. 5. clipmem get <snapshot_id> --format json — nested item/representation detail for a snapshot you already have. 6. clipmem restore <snapshot_id> — restore the full stored representation set for a snapshot back onto the macOS clipboard. 7. clipmem export <snapshot_id> --item <n> --uti <uti> --out <path> [--force] — raw bytes for binary/image/PDF payloads. 8. clipmem forget <snapshot_id> — hard-delete one snapshot and its capture history. 9. clipmem purge --older-than <duration> [--dry-run] — prune by last_observed_at. 10. clipmem settings show --format json — inspect persistent capture policy. 11. clipmem ocr status --format json — inspect local OCR queue and result counts. 12. clipmem ocr run [--limit N] [--snapshot ID] — backfill OCR for image snapshots.
---
Subcommand matrix
| Subcommand | Default --format | Supports toon? | Purpose |
|---|---|---|---|
recall [QUERY] | md | yes | Ranked best-first answer with alternatives |
search <QUERY> | text | yes | Lexical / FTS match over the archive |
recent | text | yes | Recent unique snapshots (deduplicated) |
timeline | text | yes | Chronological capture events (not deduped) |
get <SNAPSHOT_ID> | text | no | Nested detail for one snapshot |
restore <SNAPSHOT_ID> | text | — | Restore a stored snapshot back onto the clipboard |
export <SNAPSHOT_ID> | — (raw bytes) | — | Write one representation to disk |
forget <SNAPSHOT_ID> | text | — | Hard-delete one snapshot and its capture history |
purge | text | — | Delete old snapshots by last_observed_at |
settings show | text | no | Show persistent pause / retention / ignore-list policy |
settings pause | text | — | Persistently pause or resume capture |
settings api-key-filter | text | — | Enable or disable API key filtering |
settings ocr | text | — | Enable or disable local OCR for new image captures |
settings retention | text | — | Set retention to a duration or forever |
settings ignore add/remove/list | text (list also supports json) | no | Manage ignored bundle identifiers |
ocr status | text (json supported) | — | Local OCR queue and result counts |
ocr run | text (json supported) | — | Backfill OCR for stored image snapshots |
capture-once | — | — | Single clipboard capture (setup / ad-hoc) |
watch | — | — | Background daemon; usually a LaunchAgent |
setup | — | — | Seed one capture and start background capture |
service status | text (or --json) | — | Background provider state + capture freshness |
service start / stop / uninstall | — | — | Manage the background watcher service |
doctor | text (or --json) | — | SQLite / FTS5 diagnostics |
agents openclaw doctor | text | — | Integration health: PATH, workspace, sandbox |
agents openclaw install-skill | — | — | Write packaged skill files to disk |
agents openclaw print-skill | — | — | Print embedded SKILL.md to stdout |
agents openclaw uninstall-skill | — | — | Remove installed skill directory |
--json is a compatibility alias for --format json on search, recent, timeline, get, ocr status, ocr run, capture-once, and doctor.
---
Output formats
All retrieval commands share the same --format set except get, which omits toon:
text— human-oriented terminal output. Default forsearch,recent,timeline,get. Do not parse.md— compact markdown. Default forrecall. Human-oriented. Do not parse.json— single structured object with a stable envelope. Parse this.jsonl— newline-delimited rows. Prefer when streaming many results through a pipe.toon— flat token-efficient list. Prefer fortimeline,search,recent, andrecallwhen you only need the top fields. Unsupported onget.
---
Shared retrieval filters
search, recent, timeline, and recall accept the same filter set. get and export accept them as guards against the explicitly targeted snapshot.
Time window:
--since <RFC3339>— captures at or after this timestamp (e.g.2026-04-16T09:00:00Z).--until <RFC3339>— captures at or before this timestamp.--hours <N>— last N hours.--sincewins if both are provided.
Source:
--app <name>— case-insensitive substring match on the recorded frontmost app name.--bundle-id <id>— case-insensitive exact match on bundle identifier (e.g.com.apple.Safari).
Content shape:
--kind text|html|rtf|url|file|image|pdf|binary|other. One value per invocation.--has-text,--has-url,--has-file-url,--has-image,--has-pdf— additive presence flags (AND semantics).
Size:
--min-bytes <N>/--max-bytes <N>— applied to the total snapshot byte count.
--kind values
| Value | Matches |
|---|---|
text | plain text representations |
html | HTML clipboard payloads |
rtf | rich-text format |
url | web URLs |
file | file URLs (Finder paths) — not regular files on disk |
image | image blobs (PNG, JPEG, TIFF, etc.) |
pdf | PDF documents |
binary | opaque binary that has no safe text projection |
other | mixed or empty snapshots |
--kind file is a common pitfall: it matches clipboard-as-file-URL payloads (things dragged from Finder), not arbitrary files the user happened to reference.
---
Pagination
List commands (search, recent, timeline) accept --limit and --cursor:
--limit <N>— 1–250, default 10.--cursor <opaque>— resume from anext_cursorreturned by a prior response.
Cursors are tied to the active query, mode, and filters. Changing any of those while paginating will reject the cursor. When a response includes "truncated": true and a non-null next_cursor, there are more rows.
clipmem search "git status" --format json --limit 25
clipmem search "git status" --format json --limit 25 --cursor "<next_cursor>"---
Search modes (search, recall)
--mode auto|fts|literal, default auto.
auto— picks FTS or literal per query. Prefers literal for URLs, paths, bundle ids, dotted identifiers, and shell fragments (--flag=value, pipes, subshells). Plain prose queries try FTS first.fts— strict SQLite FTS5. Use when you want to compose boolean queries:"launchctl" AND bootstrap.literal— exact substring match. Use for punctuation-heavy strings like50%,Co-Authored-By:, or URL fragments.
Rules of thumb:
- Query contains
",AND,OR,NOT→--mode fts. - Query contains
/,.,:,%, or shell metacharacters →--mode literal. - Short natural-language query → let
--mode autopick.
---
recall extras
On top of the shared filters:
--format md|json|toon(defaultmd).--limit <N>— ranked candidates to consider (default 5).--full— expand the best candidate text instead of the compact form.--quote— force quoted best-text output.--min-score <0.0-1.0>— threshold below which a query alone is not trusted; falls back to recency / filters.--prefer-recent— bias ranking toward recency.--prefer-app <name>— bias toward matching app or bundle id.--hours <N>— window for the recent-fallback when a query is weak.
If the user has no query but said "the thing I just copied":
clipmem recall --prefer-recent --hours 24 --format json --limit 5---
get, restore, and export
clipmem get <snapshot_id> --format json # nested representation detail
clipmem get <snapshot_id> --events <N> # include last N capture events (default 10)
clipmem restore <snapshot_id> # restore the whole snapshot to the clipboard
clipmem export <snapshot_id> --item <index> --uti <uti> --out <path> [--force]get --format json flattens the common text fields on the root snapshot so agents don't have to walk the representation tree. get does not support --format toon.
restore is macOS-only and writes the full stored item/UTI/raw-byte set back onto the general pasteboard. This is a whole-snapshot restore, not a text-only approximation.
export writes raw bytes to --out. There is no --format flag. By default it creates a new file and refuses to replace an existing destination; pass --force only to replace an existing regular file. Symlink destinations are rejected. Required arguments: --item (0-based), --uti (e.g. public.png, public.utf8-plain-text, com.adobe.pdf), --out. Inspect items[].representations[].uti and size_bytes in a prior get --format json to choose the right combination.
---
forget, purge, and settings
clipmem forget <snapshot_id>
clipmem purge --older-than 30d [--dry-run]
clipmem settings show [--format json]
clipmem settings pause on|off
clipmem settings api-key-filter on|off
clipmem settings ocr on|off
clipmem settings retention <duration|forever>
clipmem settings ignore add <bundle_id>
clipmem settings ignore remove <bundle_id>
clipmem settings ignore list [--format json]
clipmem ocr status [--format json]
clipmem ocr run [--limit N] [--snapshot ID] [--retry-failed] [--format json]forget is a hard delete. It removes the snapshot row, all child items/representations, and all capture events for that snapshot id via foreign-key cascades.
purge computes age from snapshot_stats.last_observed_at, not snapshots.created_at. Duration grammar is a single integer plus one unit: Nd, Nh, or Nm.
settings is the persistent capture-policy entrypoint. Ignore matching is exact, case-insensitive bundle-id matching only. OCR is opt-in, runs locally through Apple Vision on macOS, and stores text/status separately from raw image bytes.
---
Global flags
--db <path>— override the SQLite database path. Default:~/Library/Application Support/clipmem/clipmem.sqlite3on macOS. Use this only when pointing at an alternate archive (tests, backups).
Environment
CLIPMEM_OPENCLAW_WORKSPACE— overrides the OpenClaw workspace root used byagents openclaw install-skillandagents openclaw doctor. Falls back toopenclaw config get agents.defaults.workspace, then~/.openclaw/workspace.HOME— resolves~/in default paths.
---
Exit codes
0— success1— uncategorized runtime failure2— invalid args3— not found (e.g. snapshot id, representation)4— unsupported format for this subcommand (e.g.--format toononget)5— database error6— platform error (macOS API / filesystem)
Scripts can rely on these to distinguish "no such snapshot" (retriable with a different id) from "database locked" (retry with backoff) from "wrong format" (agent bug).
---
Script-friendly guarantees
- stdout contains only the requested command output.
- stderr contains diagnostics only.
- No interactive prompts anywhere in the CLI.
- List commands use bounded
--limitdefaults and opaque cursor pagination. --format jsonoutput is stable withinschema_version: 2.
Clipboard Memory — Worked Examples
Concrete input → output walkthroughs. Byte-identical across skill packages.
Each example shows the user's question, the command to run, the shape of the response, and the next step.
---
Example 1 — "What was that URL I copied from Safari yesterday?"
The user gave a time cue (yesterday) and a source (Safari). Use recall with a --prefer-recent bias, an --app filter, and a generous --hours window.
clipmem recall "url" --prefer-recent --app safari --has-url --hours 48 --format json --limit 5Response (trimmed):
{
"schema_version": 2,
"command": "recall",
"best_candidate": {
"snapshot_id": 812,
"best_text": "https://developer.apple.com/documentation/appkit/nspasteboard",
"urls": ["https://developer.apple.com/documentation/appkit/nspasteboard"],
"app_name": "Safari",
"observed_at": "2026-04-16T17:45:00Z",
"why_matched": "url filter + recency bias"
},
"best_match_confidence": "high",
"alternatives": [ /* ... */ ],
"next_cursor": null
}Report best_candidate.urls[0]. If best_match_confidence were "low", enumerate alternatives instead.
---
Example 2 — "Show me everything I copied today, in order"
The user wants chronological events, not deduplicated recent snapshots. Use timeline and toon for efficient enumeration.
clipmem timeline --hours 24 --format toon --sort asc --limit 50TOON output (one row per line, tab-separated scalar fields):
snapshot_id observed_at app_name kind best_text
812 2026-04-17T08:02:11Z Safari url https://developer.apple.com/…
813 2026-04-17T08:04:03Z Terminal text git status
813 2026-04-17T08:11:59Z Terminal text git status
...Notice snapshot 813 appears twice — timeline shows each capture event, not each unique snapshot. If truncated shows more rows exist, re-run with the last row's time as --until or request --format json and page via --cursor.
---
Example 3 — "Pull the image I copied from that screenshot tool"
Images have no text projection. Use recall to find the snapshot, then get to discover the representation uti and byte size, then export to write raw bytes.
# 1. find the snapshot
clipmem recall "screenshot" --kind image --hours 72 --format json --limit 3{
"best_candidate": {
"snapshot_id": 901,
"kind": "image",
"best_text": null,
"total_bytes": 138402,
"app_name": "CleanShot X"
}
}# 2. inspect representations to pick a uti
clipmem get 901 --format json{
"snapshot": {
"items": [
{
"item_index": 0,
"representations": [
{ "uti": "public.png", "size_bytes": 138402, "is_indexed": false },
{ "uti": "public.tiff", "size_bytes": 412004, "is_indexed": false }
]
}
]
}
}# 3. export raw bytes
clipmem export 901 --item 0 --uti public.png --out ./clipboard.png
clipmem export 901 --item 0 --uti public.png --out ./clipboard.png --forceexport writes binary content to --out and exits 0 on success. It creates a new file by default; pass --force only to replace an existing regular file. There is no --format on export.
---
Example 4 — Paginating a large search
The user asks for everything matching a phrase. search returns bounded pages; use the cursor to keep going.
clipmem search "launchctl bootstrap" --mode literal --format json --limit 25{
"schema_version": 2,
"command": "search",
"results": [ /* 25 rows */ ],
"truncated": true,
"next_cursor": "eyJvZmZzZXQiOjI1LCJxdWVyeSI6Imxhdw..."
}clipmem search "launchctl bootstrap" --mode literal --format json --limit 25 \
--cursor "eyJvZmZzZXQiOjI1LCJxdWVyeSI6Imxhdw..."Stop paginating when truncated is false or next_cursor is null.
Cursors are tied to the active query, mode, and filters. Changing any of those mid-pagination invalidates the cursor — start over.
---
Example 5 — "Give me the exact text, not a summary"
By default recall returns a compact form. Force quoted, full text:
clipmem recall "the SQL migration" --quote --full --format jsonbest_candidate.best_text now holds the complete stored text. If it's still truncated (very large clipboards), use get --format json and concatenate text_fragments[].text.
---
Example 6 — "Nothing is copied from today" — diagnose first
Don't assume the archive is wrong; the watcher may have stopped.
./scripts/check-setup.sh
# or, inline
clipmem doctor --json
clipmem service status --jsonIf clipmem service status --json reports stale: true, the watcher is not running. Tell the user to run clipmem setup or brew services start clipmem before retrying.
See troubleshooting.md for remediation steps.
Clipboard Memory — JSON Schema
Stable response shapes for --format json. Current schema_version is 2. This file is kept byte-identical across skill packages.
Breaking changes to these fields will bump schema_version. Additive changes (new optional keys) are allowed within the same version.
---
Shared envelope (recall, search, recent, timeline)
{
"schema_version": 2,
"command": "recall",
"generated_at": "2026-04-17T12:34:56Z",
"applied_filters": { "hours": 24, "app": "safari" },
"truncated": false,
"next_cursor": null,
"results": [ /* rows, see below */ ]
}schema_version— integer. Pin to2for stability checks.command— echoes the subcommand.generated_at— RFC3339 timestamp when the response was produced.applied_filters— echoes the filters actually applied after argument parsing.truncated—truewhen more rows exist beyond--limit.next_cursor— opaque string to pass back as--cursorwhentruncatedistrue.nullwhen there are no more rows.results— list of flattened snapshot rows.
recall adds three extras at the top level:
best_candidate— the top-ranked row (also appears asresults[0]).why_selected— short string explaining whybest_candidatewas picked.best_match_confidence—"high" | "medium" | "low".best_match_score— float in[0.0, 1.0].quoted_text— present only when--quoteis set and usable text exists.
---
Flattened snapshot row (in results[] and best_candidate)
Read these first; walk nested items[].representations[] only after get.
{
"snapshot_id": 42,
"event_id": 1000,
"sha256": "<hex>",
"kind": "text",
"observed_at": "2026-04-17T12:00:00Z",
"first_seen_at": "2026-04-17T11:00:00Z",
"last_seen_at": "2026-04-17T12:00:00Z",
"app_name": "Terminal",
"app_bundle_id": "com.apple.Terminal",
"best_text": "git status",
"best_text_uti": "public.utf8-plain-text",
"text_fragments": [{ "representation": "public.utf8-plain-text", "text": "git status" }],
"urls": [],
"file_paths": [],
"html_text": null,
"rtf_text": null,
"ocr_text": null,
"ocr_status": null,
"text_summary": "git status",
"preview_text": "git status",
"item_count": 1,
"total_bytes": 10,
"capture_count": 3,
"score": 0.95,
"why_matched": "full phrase match",
"matched_fields": ["search_text"],
"snippet": "git status"
}Fields to read first for common questions:
| Intent | Read |
|---|---|
| "what was the text" | best_text (fall back to text_summary, preview_text) |
| "what URL" | urls (array) |
| "what file / path" | file_paths (array) |
| "which app" | app_name / app_bundle_id |
| "when" | observed_at, first_seen_at, last_seen_at |
| "is this binary / image / pdf" | kind, presence of best_text, total_bytes |
| "why did recall pick this" | why_matched, matched_fields, score |
best_text can come from OCR for image-only snapshots. In that case, best_text_uti is "com.clipmem.ocr.text" and ocr_status is "ready". If binary-only snapshots have no OCR text, fall through to clipmem export with a uti drawn from clipmem get.
---
clipmem get --format json
{
"schema_version": 2,
"command": "get",
"generated_at": "2026-04-17T12:34:56Z",
"applied_filters": { },
"snapshot": {
"snapshot_id": 42,
"sha256": "<hex>",
"kind": "text",
"best_text": "git status",
"best_text_uti": "public.utf8-plain-text",
"text_fragments": [ /* ... */ ],
"urls": [],
"file_paths": [],
"html_text": null,
"rtf_text": null,
"ocr_text": null,
"ocr_status": null,
"text_summary": "git status",
"preview_text": "git status",
"search_text": "git status",
"item_count": 1,
"total_bytes": 10,
"created_at": "2026-04-17T11:00:00Z",
"capture_count": 3,
"first_observed_at": "2026-04-17T11:00:00Z",
"last_observed_at": "2026-04-17T12:00:00Z",
"last_frontmost_app_name": "Terminal",
"last_frontmost_app_bundle_id": "com.apple.Terminal",
"recent_events": [
{ "event_id": 1000, "observed_at": "2026-04-17T12:00:00Z", "change_count": 123 }
],
"items": [
{
"item_index": 0,
"representations": [
{
"uti": "public.utf8-plain-text",
"size_bytes": 10,
"is_indexed": true
}
]
}
]
}
}Raw bytes are not included in get --format json. The items[].representations[] tree gives you the uti and size_bytes needed to call clipmem export. get does not accept --format toon.
---
clipmem capture-once --json
Returns a single snapshot envelope similar to get, describing what was just captured.
---
clipmem doctor --json
{
"db_path": "/Users/you/Library/Application Support/clipmem/clipmem.sqlite3",
"sqlite_version": "3.45.1",
"journal_mode": "wal",
"fts5_compile_option_present": true,
"fts5_create_virtual_table_ok": true,
"compile_options": ["ENABLE_FTS5", "ENABLE_RTREE", "…"]
}clipmem doctor communicates failure via exit code (non-zero means the SQLite archive is corrupt, missing, or unreadable), not via a JSON errors field. fts5_create_virtual_table_ok: false means --mode fts will fail; use --mode literal instead. fts5_compile_option_present is the weaker "SQLite was built with FTS5 support" signal and is not sufficient on its own.
---
Top-level keys an agent should always check
Before trusting a response:
1. schema_version == 2. 2. For envelopes: truncated and next_cursor before concluding "there is nothing else". 3. For rows: best_text nullability before claiming "I found the exact text". 4. For recall: best_match_confidence before committing to best_candidate. On "low", surface alternatives.
Clipboard Memory — Setup Check
Prose mirror of scripts/check-setup.sh. Use this when your runtime can't execute shell scripts directly. Byte-identical across skill packages.
Run these commands in order. Stop at the first failure and repair before querying.
1. Binary present
clipmem --versionExpect a version line on stdout, exit code 0. If not, clipmem is missing from PATH. Install via brew install tristanmanchester/tap/clipmem or cargo install clipmem.
2. Database healthy
clipmem doctor --jsonExpect exit 0 and "fts5_create_virtual_table_ok": true in the JSON payload. Non-zero exit means the SQLite archive is corrupt or inaccessible (failure is signalled via exit code, not a JSON errors field). fts5_create_virtual_table_ok: false means FTS queries will fail — either use --mode literal or rebuild the database.
3. Service and watcher freshness
clipmem service status --jsonExpect stale: false. The report also tells you whether the Homebrew service (homebrew.mxcl.clipmem) or the direct LaunchAgent (io.openclaw.clipmem.watch) is loaded and running.
If the report says no background service is loaded, start one of these:
clipmem setup
# or
brew services start clipmem4. OpenClaw integration (optional)
If the agent is OpenClaw, also check:
clipmem agents openclaw doctorExpect every check to report [OK]. [FAIL] lines include remediation steps.
Interpretation
| Symptom | Likely cause |
|---|---|
clipmem not found | binary not installed or not on PATH |
doctor exits non-zero | database lock, corruption, or permission issue |
service status --json reports stale: true | no recent captures and no background watcher running |
| FTS query errors | fts5_create_virtual_table_ok: false — switch to --mode literal |
| Sandboxed agent can't see the archive | PATH or file-access scope; rerun openclaw sandbox explain |
See scripts/check-setup.sh for the executable version with categorised exit codes (0 healthy, 1 watcher stale, 2 binary missing, 3 doctor failed).
Clipboard Memory — Troubleshooting
Diagnose before reinterpreting. Most "nothing found" outcomes are a stale watcher or a mismatched filter, not a true miss.
Start by running `scripts/check-setup.sh` or the prose in setup-check.md.
---
Empty or weak recall result
Do these in order, stopping when the result improves:
1. Widen the time window. --hours 72, or drop --hours entirely. 2. Remove source filters. The user's memory of which app doesn't always match what clipmem recorded as the frontmost process. 3. Switch to `timeline`. If the user said "today" or "yesterday", chronological order + filters often finds things recall's ranker misses. 4. Switch to `search`. For exact phrases or punctuation-heavy strings, try --mode literal. 5. Loosen content shape. Drop --kind, --has-url, --has-text flags — they may be excluding the right snapshot.
clipmem recall "<query>" --hours 72 --format json
clipmem timeline --hours 72 --format json --limit 25
clipmem search "<query>" --mode literal --format json---
Watcher not running
Symptom: clipmem timeline --hours 1 returns zero rows despite the user having copied recently.
clipmem service status --jsonIf stale: true or neither the Homebrew service nor the direct LaunchAgent is running:
clipmem setup
# or, for Homebrew-native management:
brew services start clipmem---
FTS mode failures
clipmem search "..." --mode fts errors with fts5: syntax error or similar:
- Punctuation in the query confuses FTS5. Switch to
--mode literal. - Check
clipmem doctor --jsonfor"fts5_create_virtual_table_ok": true. If false, the SQLite build lacks usable FTS5 — every--mode ftscall will fail. - Mix of quotes and operators (
"foo" AND bar) should parse in FTS5. Unbalanced quotes do not.
---
Binary-only snapshots (images, PDFs, opaque blobs)
Symptom: best_text is null or empty, yet total_bytes > 0 and kind is image, pdf, or binary.
This is expected — those clipboards have no safe text projection. To recover the content:
1. Call clipmem get <snapshot_id> --format json. 2. Inspect items[].representations[] for a useful uti (e.g. public.png, com.adobe.pdf). 3. Call clipmem export <snapshot_id> --item <index> --uti <uti> --out <path>.
When no usable best_text exists, report the metadata honestly:
"I found the clipboard item (snapshot 901, PNG from CleanShot X at 10:12 today). It has no stored text — I'd need to export the raw image to recover the content."
Do not invent exact text that was never captured as text.
---
Sandbox / PATH issues
Symptom: the agent's runtime cannot execute clipmem even though it runs fine in the user's shell.
1. Confirm clipmem is on the agent's PATH, not just the user's shell PATH. 2. If sandboxing is in play (OpenClaw containers, Apple sandbox, etc.), the binary and the SQLite file at ~/Library/Application Support/clipmem/clipmem.sqlite3 must both be visible inside the sandbox. 3. If the binary was installed after the sandbox was created, the sandbox image may need to be recreated.
If available, openclaw sandbox explain is the fastest way to see the visible PATH and file-access scope.
---
Locked or corrupt database
Symptom: clipmem doctor --json includes errors, or retrieval commands exit with code 5.
clipmem doctor --jsondatabase is locked— another writer is holding the lock. Usually the watcher under heavy load; try again in a few seconds.incompatible prerelease schema— an older archive format is being mistaken for the current DB. Move the file aside, then runclipmem setup.malformedorcorrupt— SQLite detected structural damage. Back up~/Library/Application Support/clipmem/clipmem.sqlite3, then delete and letclipmem capture-oncerebuild. You will lose history.permission denied— the database file is not writable by the current user. Check0600on the file and0700on the containing directory (~/Library/Application Support/clipmem/).
---
Exit code reference
| Code | Meaning | Typical response |
|---|---|---|
0 | success | continue |
1 | uncategorized runtime failure | inspect stderr; try again |
2 | invalid args | agent bug — check flags and re-invoke |
3 | not found | snapshot id, representation, or query returned no hits |
4 | unsupported format | wrong --format for this subcommand (e.g. toon on get) |
5 | database error | see "Locked or corrupt database" above |
6 | platform error | macOS API / filesystem issue; user action likely needed |
---
When to give up gracefully
If after all of the above the archive genuinely has no match:
- Say so plainly. Don't hallucinate.
- Quote the nearest metadata hits:
app_name,observed_at,kind. - Suggest the user copy the item again and retry —
clipmemcaptures in real time.
#!/bin/sh
# clipmem skill — setup health check
#
# Verifies that clipmem is installed, its database is healthy, the watcher
# daemon has recent captures, and (optionally) the OpenClaw integration is
# wired up.
#
# Usage:
# scripts/check-setup.sh
# scripts/check-setup.sh --json
# scripts/check-setup.sh --help
#
# Exit codes:
# 0 all required checks passed
# 1 watcher stale: clipmem is installed and healthy but nothing captured
# recently and no background service is running
# 2 binary missing: clipmem is not on PATH
# 3 doctor/service status failed or status JSON could not be parsed
# 64 invalid command-line usage
set -u
JSON_MODE=0
usage() {
cat <<'EOF'
Usage: scripts/check-setup.sh [--json] [--help]
Verify that clipmem is installed, its SQLite archive is healthy, and a
background watcher has captured something recently.
Options:
--json Emit structured JSON to stdout instead of human-oriented text
--help Show this help text and exit
Exit codes:
0 all required checks passed
1 watcher stale (no recent captures and no running background service)
2 clipmem missing from PATH
3 doctor/service status failed or status JSON could not be parsed
64 invalid command-line usage
EOF
}
while [ "$#" -gt 0 ]; do
case "$1" in
--json)
JSON_MODE=1
;;
--help|-h)
usage
exit 0
;;
*)
printf 'Error: unknown argument: %s\n\n' "$1" >&2
usage >&2
exit 64
;;
esac
shift
done
if [ "$JSON_MODE" -eq 1 ] || [ ! -t 1 ] || [ -n "${NO_COLOR:-}" ]; then
red() { printf '%s\n' "$*"; }
yellow() { printf '%s\n' "$*"; }
green() { printf '%s\n' "$*"; }
else
red() { printf '\033[31m%s\033[0m\n' "$*"; }
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }
fi
CLIPMEM_PRESENT="0"
DOCTOR_OK=""
FTS5_AVAILABLE=""
HOMEBREW_RUNNING=""
HOMEBREW_LOADED=""
LAUNCHAGENT_RUNNING=""
LAUNCHAGENT_LOADED=""
STALE=""
RECENT_CAPTURE_WITHIN_LAST_HOUR=""
CONFLICT=""
OPENCLAW_DOCTOR_OK=""
VERSION=""
SUMMARY=""
DETAILS=""
emit_json_and_exit() {
JSON_EXIT_CODE="$1"
SUMMARY="$2"
export JSON_EXIT_CODE SUMMARY
export CLIPMEM_PRESENT DOCTOR_OK FTS5_AVAILABLE HOMEBREW_RUNNING HOMEBREW_LOADED
export LAUNCHAGENT_RUNNING LAUNCHAGENT_LOADED STALE RECENT_CAPTURE_WITHIN_LAST_HOUR
export CONFLICT OPENCLAW_DOCTOR_OK VERSION DETAILS
python3 - <<'PYJSON'
import json
import os
def parse_boolish(value):
if value in (None, '', '-1'):
return None
return value == '1'
data = {
'ok': os.environ.get('JSON_EXIT_CODE') == '0',
'exit_code': int(os.environ.get('JSON_EXIT_CODE', '0')),
'summary': os.environ.get('SUMMARY', ''),
'details': os.environ.get('DETAILS') or None,
'clipmem_present': parse_boolish(os.environ.get('CLIPMEM_PRESENT')),
'doctor_ok': parse_boolish(os.environ.get('DOCTOR_OK')),
'fts5_available': parse_boolish(os.environ.get('FTS5_AVAILABLE')),
'homebrew_running': parse_boolish(os.environ.get('HOMEBREW_RUNNING')),
'homebrew_loaded': parse_boolish(os.environ.get('HOMEBREW_LOADED')),
'launchagent_running': parse_boolish(os.environ.get('LAUNCHAGENT_RUNNING')),
'launchagent_loaded': parse_boolish(os.environ.get('LAUNCHAGENT_LOADED')),
'stale': parse_boolish(os.environ.get('STALE')),
'recent_capture_within_last_hour': parse_boolish(os.environ.get('RECENT_CAPTURE_WITHIN_LAST_HOUR')),
'conflict': parse_boolish(os.environ.get('CONFLICT')),
'openclaw_doctor_ok': parse_boolish(os.environ.get('OPENCLAW_DOCTOR_OK')),
'version': os.environ.get('VERSION') or None,
}
print(json.dumps(data, indent=2, sort_keys=True))
PYJSON
exit "$JSON_EXIT_CODE"
}
fail() {
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit "$2" "$1"
fi
red "FAIL: $1"
exit "$2"
}
if ! command -v clipmem >/dev/null 2>&1; then
CLIPMEM_PRESENT="0"
fail "clipmem is not on PATH. Install via 'brew install tristanmanchester/tap/clipmem' or 'cargo install clipmem'." 2
fi
CLIPMEM_PRESENT="1"
VERSION=$(clipmem --version 2>/dev/null || true)
if [ "$JSON_MODE" -eq 0 ]; then
green "OK: ${VERSION:-clipmem present}"
fi
DOCTOR_OUT=$(clipmem doctor --json 2>&1)
DOCTOR_STATUS=$?
if [ "$DOCTOR_STATUS" -ne 0 ]; then
DOCTOR_OK="0"
DETAILS="$DOCTOR_OUT"
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit 3 "clipmem doctor failed"
fi
red "FAIL: clipmem doctor exited ${DOCTOR_STATUS}"
printf '%s\n' "$DOCTOR_OUT" >&2
exit 3
fi
DOCTOR_OK="1"
if [ "$JSON_MODE" -eq 0 ]; then
green "OK: clipmem doctor exited cleanly"
fi
if printf '%s' "$DOCTOR_OUT" | grep -Eq '"fts5_create_virtual_table_ok"[[:space:]]*:[[:space:]]*true'; then
FTS5_AVAILABLE="1"
if [ "$JSON_MODE" -eq 0 ]; then
green "OK: FTS5 available"
fi
else
FTS5_AVAILABLE="0"
if [ "$JSON_MODE" -eq 0 ]; then
yellow "WARN: FTS5 not available; --mode fts will fail. Use --mode literal."
fi
fi
STATUS_OUT=$(clipmem service status --json 2>&1)
STATUS_CODE=$?
if [ "$STATUS_CODE" -ne 0 ]; then
DETAILS="$STATUS_OUT"
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit 3 "clipmem service status failed"
fi
red "FAIL: clipmem service status exited ${STATUS_CODE}"
printf '%s\n' "$STATUS_OUT" >&2
exit 3
fi
STATUS_VARS=$(
printf '%s' "$STATUS_OUT" | python3 -c "import json,sys; data=json.load(sys.stdin); print('homebrew_running=%d' % (1 if data['homebrew']['running'] else 0)); print('homebrew_loaded=%d' % (1 if data['homebrew']['loaded'] else 0)); print('launchagent_running=%d' % (1 if data['launchagent']['running'] else 0)); print('launchagent_loaded=%d' % (1 if data['launchagent']['loaded'] else 0)); print('stale=%d' % (1 if data['stale'] else 0)); fresh=data.get('recent_capture_within_last_hour'); print('recent_capture_within_last_hour=%s' % ('-1' if fresh is None else ('1' if fresh else '0'))); print('conflict=%d' % (1 if data.get('conflict') else 0))"
) || {
DETAILS="$STATUS_OUT"
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit 3 "could not parse clipmem service status JSON"
fi
red "FAIL: could not parse clipmem service status JSON"
printf '%s\n' "$STATUS_OUT" >&2
exit 3
}
eval "$STATUS_VARS"
HOMEBREW_RUNNING="$homebrew_running"
HOMEBREW_LOADED="$homebrew_loaded"
LAUNCHAGENT_RUNNING="$launchagent_running"
LAUNCHAGENT_LOADED="$launchagent_loaded"
STALE="$stale"
RECENT_CAPTURE_WITHIN_LAST_HOUR="$recent_capture_within_last_hour"
CONFLICT="$conflict"
if [ "$JSON_MODE" -eq 0 ]; then
if [ "${homebrew_running}" -eq 1 ]; then
green "OK: Homebrew service homebrew.mxcl.clipmem is running"
elif [ "${homebrew_loaded}" -eq 1 ]; then
yellow "WARN: Homebrew service homebrew.mxcl.clipmem is loaded but not running"
fi
if [ "${launchagent_running}" -eq 1 ]; then
green "OK: LaunchAgent io.openclaw.clipmem.watch is running"
elif [ "${launchagent_loaded}" -eq 1 ]; then
yellow "WARN: LaunchAgent io.openclaw.clipmem.watch is loaded but not running"
fi
if [ "${homebrew_running}" -eq 0 ] && [ "${homebrew_loaded}" -eq 0 ] \
&& [ "${launchagent_running}" -eq 0 ] && [ "${launchagent_loaded}" -eq 0 ]; then
yellow "WARN: no clipmem background service is loaded"
yellow " Run: clipmem setup"
yellow " Or: brew services start clipmem"
fi
if [ "${recent_capture_within_last_hour}" -eq 1 ]; then
green "OK: clipboard capture observed in the last hour"
elif [ "${recent_capture_within_last_hour}" -eq 0 ]; then
yellow "WARN: no clipboard captures in the last hour"
fi
if [ "${conflict}" -eq 1 ]; then
yellow "WARN: both Homebrew and direct LaunchAgent services are installed"
yellow " Remove one with: brew services stop clipmem"
yellow " Or: clipmem service uninstall"
fi
fi
if clipmem agents openclaw --help >/dev/null 2>&1; then
if clipmem agents openclaw doctor >/dev/null 2>&1; then
OPENCLAW_DOCTOR_OK="1"
if [ "$JSON_MODE" -eq 0 ]; then
green "OK: clipmem agents openclaw doctor passed"
fi
else
OPENCLAW_DOCTOR_OK="0"
if [ "$JSON_MODE" -eq 0 ]; then
yellow "WARN: clipmem agents openclaw doctor reported issues; run it directly for details"
fi
fi
fi
if [ "${STALE}" -eq 1 ]; then
SUMMARY="STALE: no recent captures and no background watcher is running. Run 'clipmem setup' or 'brew services start clipmem' and retry."
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit 1 "$SUMMARY"
fi
red "$SUMMARY"
exit 1
fi
SUMMARY="All checks passed."
if [ "$JSON_MODE" -eq 1 ]; then
emit_json_and_exit 0 "$SUMMARY"
fi
green "$SUMMARY"
exit 0