aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2026-01-26 01:12:37 +0000
committerGitHub <noreply@github.com>2026-01-26 01:12:37 +0000
commit42cdc937c867f5e35c75534f55caea51c60d388a (patch)
tree4676519f6b557c00f0598d4e7705dc08cd87f1f5 /apps
parent5656e394d3d879d9cd48c18400cd7ce4c12f416e (diff)
downloadkarakeep-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.ts89
-rw-r--r--apps/cli/src/commands/lists.ts11
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)];
}