···
+
(** List bookmarks command *)
+
let list_bookmarks env _xdg profile base_url limit archived favourited tags =
+
Eio.Switch.run @@ fun sw ->
+
let api_key = Keyeio.Profile.get_required profile ~key:"api_key" in
+
Keyeio.Profile.get profile ~key:"base_url"
+
|> Option.value ~default:"https://hoard.recoil.org"
+
Printf.printf "Fetching bookmarks from %s...\n" base_url;
+
let bookmarks = Karakeepe.fetch_all_bookmarks
+
?filter_tags:(if tags = [] then None else Some tags)
+
(* Filter by archived/favourited if requested *)
+
List.filter (fun (b : Karakeepe.bookmark) ->
+
| Some true -> b.archived
+
| Some false -> not b.archived
+
| Some true -> b.favourited
+
| Some false -> not b.favourited
+
Printf.printf "Found %d bookmarks\n\n" (List.length bookmarks);
+
List.iteri (fun i (b : Karakeepe.bookmark) ->
+
Printf.printf "%d. %s\n" (i + 1) b.url;
+
| Some title -> Printf.printf " Title: %s\n" title
+
Printf.printf " ID: %s\n" b.id;
+
Printf.printf " Created: %s\n" (Ptime.to_rfc3339 b.created_at);
+
Printf.printf " Tags: %s\n" (String.concat ", " b.tags);
+
if b.archived then Printf.printf " [ARCHIVED]\n";
+
if b.favourited then Printf.printf " [FAVOURITED]\n";
+
| Some s when s <> "" ->
+
let summary = if String.length s > 100 then String.sub s 0 100 ^ "..." else s in
+
Printf.printf " Summary: %s\n" summary
+
Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
+
(** Get a single bookmark by ID *)
+
let get_bookmark env _xdg profile base_url bookmark_id =
+
Eio.Switch.run @@ fun sw ->
+
let api_key = Keyeio.Profile.get_required profile ~key:"api_key" in
+
Keyeio.Profile.get profile ~key:"base_url"
+
|> Option.value ~default:"https://hoard.recoil.org"
+
let bookmark = Karakeepe.fetch_bookmark_details ~sw ~env ~api_key base_url bookmark_id in
+
Printf.printf "Bookmark: %s\n" bookmark.url;
+
Printf.printf "ID: %s\n" bookmark.id;
+
(match bookmark.title with
+
| Some title -> Printf.printf "Title: %s\n" title
+
(match bookmark.note with
+
| Some note -> Printf.printf "Note: %s\n" note
+
Printf.printf "Created: %s\n" (Ptime.to_rfc3339 bookmark.created_at);
+
(match bookmark.updated_at with
+
| Some t -> Printf.printf "Updated: %s\n" (Ptime.to_rfc3339 t)
+
if bookmark.tags <> [] then
+
Printf.printf "Tags: %s\n" (String.concat ", " bookmark.tags);
+
if bookmark.archived then Printf.printf "Status: ARCHIVED\n";
+
if bookmark.favourited then Printf.printf "Status: FAVOURITED\n";
+
(match bookmark.summary with
+
| Some s when s <> "" -> Printf.printf "\nSummary:\n%s\n" s
+
if bookmark.content <> [] then begin
+
Printf.printf "\nContent metadata:\n";
+
List.iter (fun (k, v) ->
+
if v <> "null" && v <> "" then
+
Printf.printf " %s: %s\n" k v
+
if bookmark.assets <> [] then begin
+
Printf.printf "\nAssets:\n";
+
List.iter (fun (id, asset_type) ->
+
Printf.printf " %s (%s)\n" id asset_type;
+
Printf.printf " URL: %s\n" (Karakeepe.get_asset_url base_url id)
+
Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
+
(** Create a new bookmark *)
+
let create_bookmark env _xdg profile base_url url title note tags archived favourited =
+
Eio.Switch.run @@ fun sw ->
+
let api_key = Keyeio.Profile.get_required profile ~key:"api_key" in
+
Keyeio.Profile.get profile ~key:"base_url"
+
|> Option.value ~default:"https://hoard.recoil.org"
+
Printf.printf "Creating bookmark: %s\n" url;
+
let tags_opt = if tags = [] then None else Some tags in
+
let bookmark = Karakeepe.create_bookmark
+
Printf.printf "✓ Bookmark created successfully!\n";
+
Printf.printf "ID: %s\n" bookmark.id;
+
Printf.printf "URL: %s\n" bookmark.url;
+
(match bookmark.title with
+
| Some t -> Printf.printf "Title: %s\n" t
+
if bookmark.tags <> [] then
+
Printf.printf "Tags: %s\n" (String.concat ", " bookmark.tags);
+
Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
+
(** Search bookmarks by tag *)
+
let search_bookmarks env __xdg profile base_url tags limit =
+
Eio.Switch.run @@ fun sw ->
+
let api_key = Keyeio.Profile.get_required profile ~key:"api_key" in
+
Keyeio.Profile.get profile ~key:"base_url"
+
|> Option.value ~default:"https://hoard.recoil.org"
+
if tags = [] then begin
+
Printf.eprintf "Error: At least one tag is required for search\n";
+
Printf.printf "Searching for bookmarks with tags: %s\n" (String.concat ", " tags);
+
let bookmarks = Karakeepe.fetch_all_bookmarks
+
Printf.printf "Found %d bookmarks\n\n" (List.length bookmarks);
+
List.iteri (fun i (b : Karakeepe.bookmark) ->
+
Printf.printf "%d. %s\n" (i + 1) b.url;
+
| Some title -> Printf.printf " Title: %s\n" title
+
Printf.printf " ID: %s\n" b.id;
+
Printf.printf " Tags: %s\n" (String.concat ", " b.tags);
+
Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
+
(** Command-line arguments *)
+
let doc = "Override Karakeep instance base URL" in
+
Arg.(value & opt (some string) None & info ["url"; "u"] ~docv:"URL" ~doc)
+
let doc = "Maximum number of bookmarks to fetch" in
+
Arg.(value & opt int 50 & info ["limit"; "l"] ~docv:"NUM" ~doc)
+
let doc = "Filter by archived status (true/false)" in
+
Arg.(value & opt (some bool) None & info ["archived"; "a"] ~docv:"BOOL" ~doc)
+
let doc = "Filter by favourited status (true/false)" in
+
Arg.(value & opt (some bool) None & info ["favourited"; "f"] ~docv:"BOOL" ~doc)
+
let doc = "Filter by tags (comma-separated)" in
+
Arg.(value & opt (list ~sep:',' string) [] & info ["tags"; "t"] ~docv:"TAGS" ~doc)
+
let doc = "Bookmark ID" in
+
Arg.(required & pos 0 (some string) None & info [] ~docv:"ID" ~doc)
+
let doc = "URL to bookmark" in
+
Arg.(required & pos 0 (some string) None & info [] ~docv:"URL" ~doc)
+
let doc = "Bookmark title" in
+
Arg.(value & opt (some string) None & info ["title"] ~docv:"TITLE" ~doc)
+
let doc = "Bookmark note/description" in
+
Arg.(value & opt (some string) None & info ["note"; "n"] ~docv:"NOTE" ~doc)
+
let doc = "Tags to add (comma-separated)" in
+
Arg.(value & opt (list ~sep:',' string) [] & info ["tags"; "t"] ~docv:"TAGS" ~doc)
+
let create_archived_flag =
+
let doc = "Mark bookmark as archived" in
+
Arg.(value & flag & info ["archived"; "a"] ~doc)
+
let create_favourited_flag =
+
let doc = "Mark bookmark as favourited" in
+
Arg.(value & flag & info ["favourited"; "f"] ~doc)
+
(* Init command - doesn't use eiocmd since it's creating the profile *)
+
let doc = "Initialize karakeepe credentials" in
+
`S Manpage.s_description;
+
`P "Create a new credentials file for karakeepe in the XDG config directory.";
+
`P "This will create ~/.config/karakeepe/keys/karakeepe.toml with the specified profile.";
+
`P "Initialize with prompts:";
+
`Pre " karakeepe init";
+
`P "Initialize with command-line arguments:";
+
`Pre " karakeepe init --api-key \"ak1_xxx\" --base-url \"https://hoard.example.com\"";
+
`P "Initialize a production profile:";
+
`Pre " karakeepe init --profile production --api-key \"ak1_xxx\"";
+
("api_key", None); (* Will prompt if not provided *)
+
("base_url", Some "https://hoard.recoil.org") (* Has default *)
+
Eio_main.run @@ fun env ->
+
let info = Cmd.info "init" ~doc ~man in
+
let doc = "List bookmarks" in
+
~info:(Cmd.info "list" ~doc)
+
Term.(const (fun base_url limit archived favourited tags env xdg profile ->
+
list_bookmarks env xdg profile base_url limit archived favourited tags)
+
$ base_url_arg $ limit_arg $ archived_arg $ favourited_arg $ tags_arg)
+
let doc = "Get a single bookmark by ID" in
+
~info:(Cmd.info "get" ~doc)
+
Term.(const (fun base_url bookmark_id env xdg profile ->
+
get_bookmark env xdg profile base_url bookmark_id)
+
$ base_url_arg $ bookmark_id_arg)
+
let doc = "Create a new bookmark" in
+
`S Manpage.s_description;
+
`P "Create a new bookmark in Karakeep.";
+
`P "Create a simple bookmark:";
+
`Pre " karakeepe create \"https://example.com\"";
+
`P "Create with title and tags:";
+
`Pre " karakeepe create \"https://example.com\" --title \"Example Site\" --tags \"web,example\"";
+
`P "Create as favourited:";
+
`Pre " karakeepe create \"https://example.com\" --favourited";
+
~info:(Cmd.info "create" ~doc ~man)
+
Term.(const (fun base_url url title note tags archived favourited env xdg profile ->
+
create_bookmark env xdg profile base_url url title note tags archived favourited)
+
$ base_url_arg $ url_arg $ title_arg $ note_arg $ create_tags_arg $ create_archived_flag $ create_favourited_flag)
+
let doc = "Search bookmarks by tags" in
+
`S Manpage.s_description;
+
`P "Search for bookmarks by tags.";
+
`P "Search for bookmarks with tag 'work':";
+
`Pre " karakeepe search --tags work";
+
`P "Search for bookmarks with multiple tags:";
+
`Pre " karakeepe search --tags \"work,important\"";
+
~info:(Cmd.info "search" ~doc ~man)
+
Term.(const (fun base_url tags limit env xdg profile ->
+
search_bookmarks env xdg profile base_url tags limit)
+
$ base_url_arg $ tags_arg $ limit_arg)
+
let doc = "Karakeep command-line client" in
+
`S Manpage.s_description;
+
`P "A command-line client for managing bookmarks in Karakeep (Hoarder).";
+
`P "Credentials are stored securely using keyeio in XDG config directories.";
+
`P "Initialize credentials using:";
+
`Pre " karakeepe init";
+
`P "Or manually create ~/.config/karakeepe/keys/karakeepe.toml:";
+
`Pre "[default]\napi_key = \"ak1_<key_id>_<secret>\"\nbase_url = \"https://hoard.example.com\"";
+
`P "Report bugs at https://github.com/avsm/knot/issues";
+
let info = Cmd.info "karakeepe" ~version:"0.1.0" ~doc ~man in
+
let main_cmd = Cmd.group info [init_cmd; list_cmd; get_cmd; create_cmd; search_cmd] in
+
exit (Cmd.eval' main_cmd)