diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-01-26 01:12:37 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-26 01:12:37 +0000 |
| commit | 42cdc937c867f5e35c75534f55caea51c60d388a (patch) | |
| tree | 4676519f6b557c00f0598d4e7705dc08cd87f1f5 /apps | |
| parent | 5656e394d3d879d9cd48c18400cd7ce4c12f416e (diff) | |
| download | karakeep-42cdc937c867f5e35c75534f55caea51c60d388a.tar.zst | |
feat(cli): Add bookmark search command (#2426)
* feat(cli): Add search subcommand to bookmarks
Add a new search subcommand that uses the searchBookmarks API endpoint.
The command supports:
- Full-text search with advanced query matchers (tag:, is:, list:, etc.)
- Pagination with --all flag to fetch all results
- Sorting by relevance, ascending, or descending order
- Optional full content inclusion with --include-content
- Configurable result limit per page
Example usage:
bookmarks search "is:fav tag:important"
bookmarks search "kotlin" --sort-order desc --limit 20
bookmarks search "title:api" --include-content --all
* fixes + format
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/cli/src/commands/bookmarks.ts | 89 | ||||
| -rw-r--r-- | apps/cli/src/commands/lists.ts | 11 |
2 files changed, 88 insertions, 12 deletions
diff --git a/apps/cli/src/commands/bookmarks.ts b/apps/cli/src/commands/bookmarks.ts index 021e344f..e2e8efb6 100644 --- a/apps/cli/src/commands/bookmarks.ts +++ b/apps/cli/src/commands/bookmarks.ts @@ -29,18 +29,10 @@ type Bookmark = Omit<ZBookmark, "tags"> & { }; function normalizeBookmark(bookmark: ZBookmark): Bookmark { - const ret = { + return { ...bookmark, tags: bookmark.tags.map((t) => t.name), }; - - if (ret.content.type == BookmarkTypes.LINK && ret.content.htmlContent) { - if (ret.content.htmlContent.length > 10) { - ret.content.htmlContent = - ret.content.htmlContent.substring(0, 10) + "... <CROPPED>"; - } - } - return ret; } function printBookmark(bookmark: ZBookmark) { @@ -151,10 +143,15 @@ bookmarkCmd .command("get") .description("fetch information about a bookmark") .argument("<id>", "The id of the bookmark to get") - .action(async (id) => { + .option( + "--include-content", + "include full bookmark content in results", + false, + ) + .action(async (id, opts) => { const api = getAPIClient(); await api.bookmarks.getBookmark - .query({ bookmarkId: id }) + .query({ bookmarkId: id, includeContent: opts.includeContent }) .then(printBookmark) .catch(printError(`Failed to get the bookmark with id "${id}"`)); }); @@ -254,6 +251,11 @@ bookmarkCmd false, ) .option("--list-id <id>", "if set, only items from that list will be fetched") + .option( + "--include-content", + "include full bookmark content in results", + false, + ) .action(async (opts) => { const api = getAPIClient(); @@ -262,6 +264,7 @@ bookmarkCmd listId: opts.listId, limit: MAX_NUM_BOOKMARKS_PER_PAGE, useCursorV2: true, + includeContent: opts.includeContent, }; try { @@ -282,6 +285,70 @@ bookmarkCmd }); bookmarkCmd + .command("search") + .description("search bookmarks using query matchers") + .argument( + "<query>", + "the search query (supports matchers like tag:name, is:fav, etc.)", + ) + .option( + "--limit <limit>", + "number of results per page", + (val) => parseInt(val, 10), + 50, + ) + .option( + "--sort-order <order>", + "sort order for results", + (val) => { + if (val !== "relevance" && val !== "asc" && val !== "desc") { + throw new Error("sort-order must be one of: relevance, asc, desc"); + } + return val; + }, + "relevance", + ) + .option( + "--include-content", + "include full bookmark content in results", + false, + ) + .option("--all", "fetch all results (paginate through all pages)", false) + .action(async (query, opts) => { + const api = getAPIClient(); + + const request = { + text: query, + limit: opts.limit, + sortOrder: opts.sortOrder as "relevance" | "asc" | "desc", + includeContent: opts.includeContent, + }; + + try { + let resp = await api.bookmarks.searchBookmarks.query(request); + let results: ZBookmark[] = resp.bookmarks; + + // If --all flag is set, fetch all pages + if (opts.all) { + while (resp.nextCursor) { + resp = await api.bookmarks.searchBookmarks.query({ + ...request, + cursor: resp.nextCursor, + }); + results = [...results, ...resp.bookmarks]; + } + } + + printObject(results.map(normalizeBookmark), { maxArrayLength: null }); + } catch (error) { + printStatusMessage(false, "Failed to search bookmarks"); + if (error instanceof Error) { + printStatusMessage(false, error.message); + } + } + }); + +bookmarkCmd .command("delete") .description("delete a bookmark") .argument("<id>", "the id of the bookmark to delete") diff --git a/apps/cli/src/commands/lists.ts b/apps/cli/src/commands/lists.ts index 864fa790..1d9341d7 100644 --- a/apps/cli/src/commands/lists.ts +++ b/apps/cli/src/commands/lists.ts @@ -86,15 +86,24 @@ listsCmd .command("get") .description("gets all the ids of the bookmarks assigned to the list") .requiredOption("--list <id>", "the id of the list") + .option( + "--include-content", + "include full bookmark content in results", + false, + ) .action(async (opts) => { const api = getAPIClient(); try { - let resp = await api.bookmarks.getBookmarks.query({ listId: opts.list }); + let resp = await api.bookmarks.getBookmarks.query({ + listId: opts.list, + includeContent: opts.includeContent, + }); let results: string[] = resp.bookmarks.map((b) => b.id); while (resp.nextCursor) { resp = await api.bookmarks.getBookmarks.query({ listId: opts.list, cursor: resp.nextCursor, + includeContent: opts.includeContent, }); results = [...results, ...resp.bookmarks.map((b) => b.id)]; } |
