Install
openclaw skills install @linkly-ai/linkly-aiSearch, browse, read, and take notes across the user's documents indexed by Linkly AI — local files and linked cloud libraries. Use when the user asks to 'search my documents', 'find files about a topic', 'read a local document', 'what's in this folder', 'list the files in that library', 'browse doc
openclaw skills install @linkly-ai/linkly-aiLinkly AI indexes documents on the user's local machine (PDF, Markdown, DOCX, PPTX, EPUB, TXT, HTML, images, audio, video) and can also reach cloud libraries the user has linked via Linkly Web. It exposes them through a progressive disclosure workflow: search → grep or outline → read. It can also capture and list the user's local Markdown notes.
Before executing any document operation, detect what's available and pick a mode. CLI and MCP are two independent access paths — check both, don't treat MCP as a CLI fallback.
Run both checks independently (skip a check if its prerequisite isn't there):
linkly --version. Success → CLI is installed. Then run linkly status to confirm the desktop app is reachable; if the status reports a connection problem, run linkly doctor (see references/troubleshooting.md).search, find_paths, list, outline, grep, read, list_libraries, explore, and note_save are accessible in the current environment. Both servers expose all nine: the linkly-ai server (local Desktop MCP) and the linkly-ai-cloud server (the mcp.linkly.ai cloud gateway). The difference is reach, not the tool list — see "Know what your connection reaches" below. note_save is the one tool whose reach never varies: it always resolves to the user's Desktop, whichever server it arrived from.| Available | Action |
|---|---|
| Both CLI and MCP | Prefer CLI mode — clearer error messages and exit codes are easier to surface back to the user. |
| CLI only | Use CLI mode. |
| MCP only | Use MCP mode. This is the normal state for sandboxed agent environments such as Claude Code, Typeless, or Cursor with a restricted shell — the desktop app and MCP integration are fully configured but the CLI binary isn't installed inside the sandbox. Don't tell the user to install the CLI; MCP is sufficient. |
| Neither | If Bash works, recommend installing the CLI: Install Linkly AI CLI. Otherwise inform the user that Linkly AI requires either the CLI or the MCP integration and stop. |
The connection mode decides which content is visible, and no tool call can cross that boundary. This is the single most common source of "the document is there but Linkly can't find it".
| Connection | Reaches |
|---|---|
Local (linkly default) / LAN (--endpoint), or a linkly-ai MCP server on localhost | The user's local indexed content only. Cloud libraries are not reachable — cloud:// references are rejected on this path. |
Cloud gateway (linkly --remote, linkly mcp --remote, or the linkly-ai-cloud MCP server) | Both local content (through the desktop tunnel) and linked cloud libraries. |
If the user asks for cloud-library content while you are on a local or LAN connection, tell them to switch connection (--remote, or configure the cloud gateway connector). Do not retry, and do not attempt a cloud:// reference from a local connection — it will fail every time.
Notes sit outside this table. They are local files with no cloud counterpart, so note_save and list with scope="notes" always reach the Desktop no matter which connection you are on — over the tunnel when you are on the cloud gateway. Consequences: they need the Desktop online (over the tunnel, that also means Pro), and when it is unreachable there is no cloud fallback to retry against. Never pass a library alongside scope="notes", or to note_save at all — notes are a single local container and the call is rejected.
list straddles the table, by scope: folder, notes, and a local:// library are Desktop-only and ride the same paths as everything else local; scope="library" with a cloud://owner/slug is served by the gateway itself — reachable on the Free plan, and still answerable while the Desktop is offline.
The CLI's three connection modes:
~/.linkly/port. Requires the app running locally.--endpoint <url> --token <token> reaches a Linkly AI instance on the local network.--remote connects through the https://mcp.linkly.ai gateway. Linked cloud libraries are served by the gateway and stay reachable even when the desktop is offline; local content additionally needs the desktop online and its tunnel connected. Requires linkly auth set-key <api-key> first. (Reaching local content over the tunnel is a Pro feature; linked cloud libraries are served on all plans.)If you have no path to Linkly at all (neither CLI nor an MCP connection), tell the user instead of retrying.
See references/mcp-tools-reference.md for MCP parameter schemas and response formats.
Two entry points, picked by what the user handed you:
search (Step 1). Ranked and capped by limit, so it answers "what is relevant", not "what is there".list (Step 0b). Complete, paginated enumeration — no query invented, nothing hidden behind relevance.find_paths (Step 0) turns a fuzzy container name into a real address for either one. Both return real doc_ids, which is what outline / grep / read need.
When the user names a container by a fuzzy or cross-language word — folder, app, project, repo, or cloud drive (e.g. "in my WeChat", "in my Notion notes", "in the linkly-ai repo", "in my iCloud Drive") — and you don't yet know the on-disk path, run find_paths first. Pass several variants in a single call, then pipe a distinctive segment of any returned folder path into linkly search as --path-glob (or, to scope to a whole folder, copy that candidate's path_glob field verbatim — it is already glob-quoted, so a folder name with * ? [ still matches literally). This also works inside a Linkly cloud library; candidates there carry a cloud://owner/slug reference to pass to the follow-up search's library (see references/mcp-tools-reference.md).
linkly find-paths --patterns WeChat,微信,wxid --limit 5
linkly search "购物订单" --path-glob "*xinWeChat*"
Skip this step for pure content queries ("find resumes"), file-type filters (use search --type pdf directly), or queries with no container intent.
Zero-directory fallback: if find_paths returns 0 directories, the patterns may have only matched filenames, not directory segments — fall back to linkly search directly (without --path-glob); the filename BM25 field will still pick those up.
For aggregation behaviour and the full when-to-use matrix, see references/search-strategies.md ("Locate the container first") and references/mcp-tools-reference.md (find_paths).
Once the container is known, list enumerates it. There is no query to invent and no ranking to hide items behind.
linkly list --scope folder --path /Users/me/Documents/reports
linkly list --scope folder --path /Users/me/notes --type md --modified-after 2026-01-01
linkly list --scope library --library my-research --limit 200 --no-snippet
linkly list --scope notes --tags project
scope="folder" — an absolute disk path; omit path to sweep every watched root. The path is an address, not a glob: no *, no fuzzy names. If you only have a fuzzy name, run Step 0 first and pass the path it returns.scope="library" — one library: local://<id>, a plain local name, or cloud://owner/slug (discover it with list_libraries). A cloud library takes a relative path prefix, never an absolute one.scope="notes" — the user's own notes; see "Notes".sort is the truncation policy, not decoration. With has_more: true you are holding the newest slice (recent, the default), the oldest (oldest), or the A→Z head (name) — never a random one. Say which slice you looked at, and page with offset when completeness matters.
Read the readme pointer only when the folder's purpose matters — "what is this folder for?", or before acting on files you don't recognize. For plain enumeration or locating one file, skip it; it costs an extra read.
total: 0 is not automatically "there's nothing there". A local list distinguishes a path that doesn't exist, a path outside the watched roots, and a directory that is genuinely empty of indexed files — the response says which, so relay that instead of reporting an empty folder. Cloud libraries cannot tell "prefix doesn't exist" from "prefix is empty" and say so in the response.
Full parameter matrix and response shapes: references/mcp-tools-reference.md (list).
Find documents matching a query. Start here for content questions — never guess document IDs; a real doc_id only ever comes from a search or list response.
linkly search "query keywords" --limit 10
linkly search "machine learning" --type pdf,md --limit 5
linkly search "API design" --library my-research --limit 10
linkly search "notes" --path-glob "*meeting-notes*"
linkly search "Q3 report" --modified-after 2024-07-01 --modified-before 2024-09-30
linkly search "weekly retro" --time-sort newest --limit 5
linkly search "购物订单" --path-glob "*xinWeChat*" --time-sort newest --limit 5
linkly search "standup recording" --type audio,video --limit 5
Search uses BM25 + vector hybrid retrieval (OR logic for keywords, semantic matching for meaning). One exception: searching a cloud library with a path, type, or time filter drops to keyword-only ranking — the vector index cannot apply those filters before its top-K cut, so they would silently eat recall. Phrase such queries with real keywords rather than a natural-language sentence. For advanced query strategies, see references/search-strategies.md.
Tips:
--type filter when the user mentions a specific format (pdf, docx, pptx, epub, md, txt, html, image, audio, video). Audio and video match against their transcripts; images and scanned PDFs against their OCR text.--library only when the user explicitly specifies a library name.--scope notes — see "Notes" below. Note that --scope notes ignores --library and --path-glob.--path-glob to filter by file path: the pattern is substring-matched against the path (it may appear anywhere — no leading/trailing * needed), always case-sensitive. * matches any chars (incl. /), ? one char. A full directory path like /Users/me/notes/ scopes to that directory. When the actual path is unknown, run Step 0 (find_paths) first.--modified-after / --modified-before (ISO 8601 UTC) for explicit windows like "in 2024" / "after July 1, 2024"; --time-sort newest|oldest|default for "most recent / earliest" without a fixed window (default or omitting the flag both keep relevance ordering). See "Tool Response Metadata" below for how to derive relative dates.doc_id — save these verbatim for subsequent steps. They are opaque strings (e.g. local://1044, or cloud://owner/slug/... for cloud documents); never reshape or strip them.Don't: guess --path-glob when the user names a fuzzy container — run find_paths (Step 0) first to get the real on-disk path.
Silent-drop check: if you used --modified-after / --modified-before / --time-sort and the response has no [meta] now= footer (Markdown) or _meta.now field (JSON), the desktop app is below v0.4.1 and silently dropped your filter. Run linkly status to confirm and ask the user to update — see references/troubleshooting.md ("Desktop app version outdated").
Get structural overviews of documents before reading.
linkly outline <ID>
linkly outline <ID1> <ID2> <ID3>
Don't mix backends: a single outline call must contain only local IDs or only cloud IDs, never both. After a mixed local + cloud search, split the IDs into separate outline calls — mixing them returns a conflict error.
When to use: The document has has_outline: true and is longer than ~50 lines.
When to skip: The document is short (<50 lines) or has has_outline: false — use grep to find specific patterns or go directly to read.
Search for exact regex pattern matches within specific documents.
linkly grep "pattern" <ID>
linkly grep "function_name" <ID> -C 3
linkly grep "error|warning" <ID> -i --mode count
When to use: You need to find specific text (names, dates, terms, identifiers, or any pattern) within known documents. When you already know the exact text to find, grep is more precise than search.
When to skip: You need to understand the overall document structure — use outline instead.
Read document content with line numbers and pagination.
linkly read <ID>
linkly read <ID> --offset 50 --limit 100
linkly read <ID> --image-text full
Reading strategies:
offset by limit on each call (e.g., offset=1 limit=200, then offset=201 limit=200).Images referenced in the text: markdown image references inside the shown line range are resolved to indexed image documents and appended as a mapping block. --image-text / image_text controls the detail: none (mapping only), abstract (default — plus a one-line excerpt and word count per image), full (plus inline OCR text). Use full only when the images carry content you actually need — it is capped at 2000 chars per image and 20000 chars total, and over-budget images silently degrade to abstract. On cloud documents full never inlines text: matched images degrade to abstract with a per-image pointer, and you read the image's own doc_id for its full text.
Don't: call read without a real doc_id from a search or list response. Document IDs are stable but never invented — guessing one returns "Document not found".
When read says the content is unavailable: some indexed files are searchable by name but have no readable body — a cloud-storage placeholder that was never downloaded, a media file with no audio track, a failed transcription, or a file whose signature doesn't match its extension. The error names the reason. Report it to the user and move on — do not re-run search and retry; the document really is in the index, it just has no text to read.
Linkly AI keeps short Markdown "card" notes in the user's local library folder. They are plain local files, never uploaded to the cloud, and they are indexed like any other document.
Because there is no cloud copy, note tools behave the same on every connection: they reach the user's Desktop, over the tunnel when you are on the cloud gateway (--remote). If the Desktop is offline the call fails and there is nothing to fall back on — report that and stop, rather than retrying against a cloud library. Do not tell the user their notes are lost; they are on that machine, just currently unreachable.
| Goal | Tool |
|---|---|
| List / browse notes, filter by tag | list with scope="notes" |
| Find notes by content | search with scope="notes" |
| Create or rewrite a note | note_save |
linkly list --scope notes
linkly list --scope notes --tags project,urgent --sort name
This is the same enumeration tool used for folders and libraries (Step 0b) — notes are one of its three scopes. It does no full-text matching; it enumerates and paginates (sort: recent default / oldest / name; use has_more to page). Two things are notes-only: every response carries available_tags — the top 50 tags actually in use across all notes, and you should reuse those values instead of inventing new ones — and each item carries a note_id + version pair, the handle note_save --mode edit needs.
linkly search "quarterly planning" --scope notes
linkly search "meeting" --scope notes --tags work
⚠️ scope="notes" ignores library and path_glob — passing them together silently drops the path/library filter rather than erroring.
linkly note-save --mode create --content "..." --tags idea
linkly note-save --mode edit --note-id <uuid> --base-version <version> --tags idea --content "..."
Five rules, all of which the server enforces:
available_tags exists for filtering, not for decorating new notes.edit requires note_id + base_version. base_version is optimistic concurrency (sha256 of the file) — read it from list. A stale value returns NOTE_VERSION_CONFLICT along with the actual version: re-read, then retry. Never overwrite blindly.#tags in the body are the note's tags — the body is the single source of truth. The tags parameter only adds (the server appends the missing #tokens to the body); remove a tag by deleting its #token from the content. Base every follow-up edit on the content returned by the previous note_save response — the server may have appended #tokens to what you sent.NOTE_INVALID_INPUT. Write plain prose and lists.note_id, timestamps, source, tags). Legacy tags stored only in YAML are materialized into the body as #tokens on the first agent edit (one-time migration).Every successful tool response carries now (ISO 8601 UTC) so you can compute relative dates ("last 7 days", "after July 1, 2024", "in 2024") without guessing from training cutoff:
[meta] now=<iso>_meta.nowErrors don't carry this. When the user phrases a relative date, take the most recent now you've seen and do the date math before passing --modified-after / --modified-before to linkly search. First-call bootstrap: if you have no prior tool response yet (e.g. the user opened with "find files from last month"), run a tiny linkly search "anything" --limit 1 first purely to capture now from the meta footer, then issue the real query. See references/mcp-tools-reference.md ("Response Metadata") for the exact format.
Libraries let you scope a search to one knowledge domain. There are two kinds:
local://<id> (a plain library name also works, for backward compatibility).cloud://<owner>/<slug> (the two-segment owner/slug form is required; a single segment is rejected).Call list_libraries to discover both kinds and their identifiers — it is the only way to learn a cloud library's cloud://owner/slug.
--library my-researchlist_libraries, then scope with library="cloud://<owner>/<slug>"list_libraries (lists both local and cloud)librarylibrarylibrary firstOmitting library does not mean "search everything." It means "search all of the user's local indexed content." Cloud libraries are a separate tier: they are never included implicitly, and each one must be named explicitly, one search call per library.
So a query that returns nothing has two possible meanings, and you should not conflate them:
When the answer might be in a cloud library — the request is open-ended ("do I have anything about X?"), or the user mentions shared / team / published material — resolve it deliberately:
list_libraries to see what is linked, then issue one search per relevant cloud library.linkly list-libraries
linkly search "deep learning" --library my-research --limit 10
The explore tool provides a bird's-eye overview of all indexed documents or a specific library. It returns document type distribution, directory structure with file counts, top keywords with source attribution, and recent activity (directories with changes in the last 7 days) — without reading any document content. For a cloud library (library="cloud://<owner>/<slug>"), it also returns the library's README (if present) before the overview.
linkly explore
linkly explore --library my-research
When to use:
When NOT to use: The user already knows what they're looking for — go directly to Search.
After getting an overview, use the top keywords, directory names, and recent activity from the explore output to craft targeted search queries with search.
When users report connection issues, search failures, or other problems with Linkly AI:
linkly doctor to diagnose. It checks port file, HTTP connectivity, app status, and MCP round-trip. Share the output with the user and follow the advice printed for each failing check.cloud://owner/slug id with list_libraries.For detailed troubleshooting steps, see references/troubleshooting.md.
doc_id comes from a real search or list response — get one before calling outline / grep / read.list, not a search query you made up: search is ranked and capped, so it answers "what is relevant", never "what is there". Chain find_paths → list → outline / read.grep instead of scanning with outline + read.explore first, then follow up with targeted searches based on the keywords and directories it reveals.library by default. Add it only when the user names a library — but remember that omitting it covers local content only, never cloud libraries.--json for search, default output for read. JSON output is easier to scan programmatically when processing many search results; default Markdown output is more readable when displaying document content to the user.linkly doctor and inform the user with actionable next steps.find_paths before search; pipe a distinctive segment into --path-glob.list and search both truncate. With has_more: true, say what the sort order means ("the 50 most recently modified") rather than presenting a page as the whole container.now from response metadata for relative dates. Use [meta] now= (Markdown) or _meta.now (JSON); never guess the current date from training cutoff.available_tags is for filtering, not for decorating new notes. note_save's tags only adds — remove a tag by deleting its #token from the note body, the source of truth for tags.read reports content unavailable (cloud placeholder, no audio track, failed transcription, signature mismatch), relay the reason and stop — re-searching and retrying will not produce text that isn't there.references/cli-reference.md — CLI installation, all commands, and options.references/mcp-tools-reference.md — MCP tool schemas, parameters, and response formats.references/search-strategies.md — Advanced query crafting, multi-round search, and complex retrieval patterns.references/troubleshooting.md — Diagnosing and resolving connection and search issues.