···2727 Arg.(required & pos 0 (some string) None & info [] ~docv:"PROFILE" ~doc)
28282929(* Helper to run with filesystem *)
3030-let with_fs f =
3131- Eio_main.run @@ fun env -> f env#fs
3030+let with_fs f = Eio_main.run @@ fun env -> f env#fs
32313332(* Login command *)
3433···3938 if api_key = "" then begin
4039 Fmt.epr "Error: API key cannot be empty.@.";
4140 1
4242- end else begin
4141+ end
4242+ else begin
4343 let creds : Karakeep_config.credentials = { api_key; base_url } in
4444 (* Determine profile name *)
4545- let profile_name = match profile with
4545+ let profile_name =
4646+ match profile with
4647 | Some p -> p
4748 | None ->
4849 let profiles = Karakeep_config.list_profiles fs in
···61626263let login_cmd () =
6364 let doc = "Configure API credentials for a Karakeep instance." in
6464- let man = [
6565- `S Manpage.s_description;
6666- `P "Prompts for an API key and saves it to the specified profile.";
6767- `P "API keys can be generated from your Karakeep instance settings.";
6868- `S "EXAMPLES";
6969- `P "$(b,karakeep auth login)";
7070- `Noblank;
7171- `P " Login with default profile.";
7272- `P "$(b,karakeep auth login --profile work --base-url https://work.example.com)";
7373- `Noblank;
7474- `P " Configure a work profile with a different instance.";
7575- ] in
6565+ let man =
6666+ [
6767+ `S Manpage.s_description;
6868+ `P "Prompts for an API key and saves it to the specified profile.";
6969+ `P "API keys can be generated from your Karakeep instance settings.";
7070+ `S "EXAMPLES";
7171+ `P "$(b,karakeep auth login)";
7272+ `Noblank;
7373+ `P " Login with default profile.";
7474+ `P
7575+ "$(b,karakeep auth login --profile work --base-url \
7676+ https://work.example.com)";
7777+ `Noblank;
7878+ `P " Configure a work profile with a different instance.";
7979+ ]
8080+ in
7681 let info = Cmd.info "login" ~doc ~man in
7782 let login' profile base_url = with_fs (login_action ~profile ~base_url) in
7883 Cmd.v info Term.(const login' $ profile_term $ base_url_term)
···8085(* Logout command *)
81868287let logout_action ~profile fs =
8383- let profile_name = match profile with
8888+ let profile_name =
8989+ match profile with
8490 | Some p -> p
8591 | None -> Karakeep_config.get_current_profile fs
8692 in
···110116 if profiles <> [] then
111117 Fmt.pr "Available profiles: %s@." (String.concat ", " profiles);
112118 Fmt.pr "@.";
113113- let profile_name = match profile with
114114- | Some p -> p
115115- | None -> current
116116- in
119119+ let profile_name = match profile with Some p -> p | None -> current in
117120 match Karakeep_config.load_credentials fs ~profile:profile_name () with
118121 | None ->
119122 Fmt.pr "Profile '%s': Not configured.@." profile_name;
···141144 if profiles = [] then begin
142145 Fmt.pr "No profiles found. Use 'karakeep auth login' to create one.@.";
143146 0
144144- end else begin
147147+ end
148148+ else begin
145149 Fmt.pr "Profiles:@.";
146146- List.iter (fun p ->
147147- let marker = if p = current then " (current)" else "" in
148148- match Karakeep_config.load_credentials fs ~profile:p () with
149149- | Some creds -> Fmt.pr " %s%s - %s@." p marker creds.base_url
150150- | None -> Fmt.pr " %s%s@." p marker
151151- ) profiles;
150150+ List.iter
151151+ (fun p ->
152152+ let marker = if p = current then " (current)" else "" in
153153+ match Karakeep_config.load_credentials fs ~profile:p () with
154154+ | Some creds -> Fmt.pr " %s%s - %s@." p marker creds.base_url
155155+ | None -> Fmt.pr " %s%s@." p marker)
156156+ profiles;
152157 0
153158 end
154159···166171 Karakeep_config.set_current_profile fs profile;
167172 Fmt.pr "Switched to profile: %s@." profile;
168173 0
169169- end else begin
174174+ end
175175+ else begin
170176 Fmt.epr "Profile '%s' not found.@." profile;
171177 if profiles <> [] then
172178 Fmt.epr "Available profiles: %s@." (String.concat ", " profiles);
···197203let profile_cmd () =
198204 let doc = "Profile management commands." in
199205 let info = Cmd.info "profile" ~doc in
200200- Cmd.group info [
201201- profile_list_cmd ();
202202- profile_switch_cmd ();
203203- profile_current_cmd ();
204204- ]
206206+ Cmd.group info
207207+ [ profile_list_cmd (); profile_switch_cmd (); profile_current_cmd () ]
205208206209(* Auth command group *)
207210208211let auth_cmd () =
209212 let doc = "Authentication commands." in
210210- let man = [
211211- `S Manpage.s_description;
212212- `P "Manage authentication credentials for Karakeep instances.";
213213- `P "Credentials are stored in ~/.config/karakeep/profiles/<name>/credentials.toml";
214214- ] in
213213+ let man =
214214+ [
215215+ `S Manpage.s_description;
216216+ `P "Manage authentication credentials for Karakeep instances.";
217217+ `P
218218+ "Credentials are stored in \
219219+ ~/.config/karakeep/profiles/<name>/credentials.toml";
220220+ ]
221221+ in
215222 let info = Cmd.info "auth" ~doc ~man in
216216- Cmd.group info [
217217- login_cmd ();
218218- logout_cmd ();
219219- status_cmd ();
220220- profile_cmd ();
221221- ]
223223+ Cmd.group info [ login_cmd (); logout_cmd (); status_cmd (); profile_cmd () ]
+2-1
lib/cmd/karakeep_auth_cmd.mli
···5566(** Karakeep authentication CLI commands.
7788- Provides commands for managing API key credentials across multiple profiles. *)
88+ Provides commands for managing API key credentials across multiple profiles.
99+*)
9101011(** {1 Command Line Terms} *)
1112
+72-40
lib/cmd/karakeep_cmd.ml
···5566open Cmdliner
7788-type config = {
99- base_url : string;
1010- api_key : string;
1111-}
88+type config = { base_url : string; api_key : string }
1291310(* Helper to read API key from file *)
1411let read_api_key_file path =
···3532let api_key_file_term =
3633 let doc = "File containing the API key (legacy, use 'auth login' instead)." in
3734 Arg.(
3838- value
3939- & opt string ".karakeep-api"
3535+ value & opt string ".karakeep-api"
4036 & info [ "api-key-file" ] ~docv:"FILE" ~doc)
41374238(* API key direct term *)
4339let api_key_direct_term =
4440 let doc = "API key for authentication (overrides profile)." in
4541 let env = Cmd.Env.info "KARAKEEP_API_KEY" ~doc in
4646- Arg.(value & opt (some string) None & info [ "api-key"; "k" ] ~docv:"KEY" ~doc ~env)
4242+ Arg.(
4343+ value
4444+ & opt (some string) None
4545+ & info [ "api-key"; "k" ] ~docv:"KEY" ~doc ~env)
47464847(* Config options from CLI - not yet resolved *)
4948type config_opt = {
···5756 let make api_key_direct base_url_opt profile api_key_file =
5857 { api_key_direct; base_url_opt; profile; api_key_file }
5958 in
6060- Term.(const make $ api_key_direct_term $ base_url_opt_term $ profile_term $ api_key_file_term)
5959+ Term.(
6060+ const make $ api_key_direct_term $ base_url_opt_term $ profile_term
6161+ $ api_key_file_term)
61626263(* Resolve config with Eio filesystem *)
6364let resolve_config ~fs (opt : config_opt) : config =
···7071 match opt.api_key_direct with
7172 | Some key ->
7273 (* Direct API key provided, use default or env base URL *)
7373- let url = match opt.base_url_opt with
7474+ let url =
7575+ match opt.base_url_opt with
7476 | Some u -> u
7577 | None -> Karakeep_config.default_base_url
7678 in
7779 (key, url)
7880 | None ->
7981 (* Check environment variable *)
8080- let env_key = try Sys.getenv "KARAKEEP_API_KEY" with Not_found -> "" in
8282+ let env_key =
8383+ try Sys.getenv "KARAKEEP_API_KEY" with Not_found -> ""
8484+ in
8185 if env_key <> "" then begin
8282- let url = match opt.base_url_opt with
8686+ let url =
8787+ match opt.base_url_opt with
8388 | Some u -> u
8484- | None -> try Sys.getenv "KARAKEEP_BASE_URL"
8585- with Not_found -> Karakeep_config.default_base_url
8989+ | None -> (
9090+ try Sys.getenv "KARAKEEP_BASE_URL"
9191+ with Not_found -> Karakeep_config.default_base_url)
8692 in
8793 (env_key, url)
8888- end else begin
9494+ end
9595+ else begin
8996 (* Try XDG profile credentials *)
9090- let profile_name = match opt.profile with
9797+ let profile_name =
9898+ match opt.profile with
9199 | Some p -> p
92100 | None -> Karakeep_config.get_current_profile fs
93101 in
9494- match Karakeep_config.load_credentials fs ~profile:profile_name () with
102102+ match
103103+ Karakeep_config.load_credentials fs ~profile:profile_name ()
104104+ with
95105 | Some creds ->
96106 (* Apply base_url override if provided *)
9797- let url = match opt.base_url_opt with
107107+ let url =
108108+ match opt.base_url_opt with
98109 | Some u -> u
99110 | None -> creds.Karakeep_config.base_url
100111 in
···103114 (* Fall back to legacy .karakeep-api file *)
104115 let file_key = read_api_key_file opt.api_key_file in
105116 if file_key <> "" then begin
106106- let url = match opt.base_url_opt with
117117+ let url =
118118+ match opt.base_url_opt with
107119 | Some u -> u
108120 | None -> Karakeep_config.default_base_url
109121 in
110122 (file_key, url)
111111- end else
112112- failwith "No credentials found. Use 'karakeep auth login' or --api-key"
123123+ end
124124+ else
125125+ failwith
126126+ "No credentials found. Use 'karakeep auth login' or --api-key"
113127 end
114128 in
115129 { base_url; api_key }
···121135122136let cursor_term =
123137 let doc = "Pagination cursor for fetching next page." in
124124- Arg.(value & opt (some string) None & info [ "cursor"; "c" ] ~docv:"CURSOR" ~doc)
138138+ Arg.(
139139+ value & opt (some string) None & info [ "cursor"; "c" ] ~docv:"CURSOR" ~doc)
125140126141(* Filter terms *)
127142let archived_term =
128143 let doc = "Filter for archived items." in
129144 let archived = (Some true, Arg.info [ "archived" ] ~doc) in
130145 let not_archived =
131131- (Some false, Arg.info [ "no-archived" ] ~doc:"Filter for non-archived items.")
146146+ ( Some false,
147147+ Arg.info [ "no-archived" ] ~doc:"Filter for non-archived items." )
132148 in
133149 Arg.(value & vflag None [ archived; not_archived ])
134150···136152 let doc = "Filter for favourited items." in
137153 let fav = (Some true, Arg.info [ "favourited"; "fav" ] ~doc) in
138154 let not_fav =
139139- (Some false, Arg.info [ "no-favourited"; "no-fav" ] ~doc:"Filter for non-favourited items.")
155155+ ( Some false,
156156+ Arg.info
157157+ [ "no-favourited"; "no-fav" ]
158158+ ~doc:"Filter for non-favourited items." )
140159 in
141160 Arg.(value & vflag None [ fav; not_fav ])
142161143162let include_content_term =
144163 let doc = "Include full content in response." in
145164 let include_it = (true, Arg.info [ "include-content" ] ~doc) in
146146- let exclude_it = (false, Arg.info [ "no-content" ] ~doc:"Exclude content from response.") in
165165+ let exclude_it =
166166+ (false, Arg.info [ "no-content" ] ~doc:"Exclude content from response.")
167167+ in
147168 Arg.(value & vflag true [ include_it; exclude_it ])
148169149170(* Entity ID terms *)
···170191171192let title_term =
172193 let doc = "Title for the bookmark." in
173173- Arg.(value & opt (some string) None & info [ "title"; "t" ] ~docv:"TITLE" ~doc)
194194+ Arg.(
195195+ value & opt (some string) None & info [ "title"; "t" ] ~docv:"TITLE" ~doc)
174196175197let note_term =
176198 let doc = "Note to attach to the bookmark." in
···203225204226let description_term =
205227 let doc = "Description for the list." in
206206- Arg.(value & opt (some string) None & info [ "description"; "d" ] ~docv:"TEXT" ~doc)
228228+ Arg.(
229229+ value
230230+ & opt (some string) None
231231+ & info [ "description"; "d" ] ~docv:"TEXT" ~doc)
207232208233let parent_id_term =
209234 let doc = "Parent list ID for nesting." in
···211236212237let query_term =
213238 let doc = "Query for smart list." in
214214- Arg.(value & opt (some string) None & info [ "query"; "q" ] ~docv:"QUERY" ~doc)
239239+ Arg.(
240240+ value & opt (some string) None & info [ "query"; "q" ] ~docv:"QUERY" ~doc)
215241216242let search_query_term =
217243 let doc = "Search query." in
···248274249275let output_format_term =
250276 let json = (Json, Arg.info [ "json"; "J" ] ~doc:"Output in JSON format.") in
251251- let ids_only = (Quiet, Arg.info [ "ids-only" ] ~doc:"Output only IDs (one per line).") in
277277+ let ids_only =
278278+ (Quiet, Arg.info [ "ids-only" ] ~doc:"Output only IDs (one per line).")
279279+ in
252280 Arg.(value & vflag Text [ json; ids_only ])
253281254282(* Logging setup *)
···277305 in
278306 f client
279307280280-(* JSON encoding helpers using jsont *)
308308+(* JSON encoding helpers using nox-json *)
281309let encode_json codec v =
282282- match Jsont_bytesrw.encode_string codec v with
283283- | Ok s -> s
284284- | Error e -> raise (Karakeep.err (Karakeep.Json_error { reason = e }))
310310+ try Json.to_string codec v
311311+ with Json.Error e ->
312312+ raise
313313+ (Karakeep.err (Karakeep.Json_error { reason = Json.Error.to_string e }))
285314286315let json_of_bookmark b = encode_json Karakeep.bookmark_jsont b
287316let json_of_tag t = encode_json Karakeep.tag_jsont t
···294323295324let print_json_array to_json items =
296325 print_string "[";
297297- List.iteri (fun i item ->
298298- if i > 0 then print_string ",";
299299- print_string (to_json item)) items;
326326+ List.iteri
327327+ (fun i item ->
328328+ if i > 0 then print_string ",";
329329+ print_string (to_json item))
330330+ items;
300331 print_endline "]"
301332302333let print_bookmark fmt (b : Karakeep.bookmark) =
···304335 | Text ->
305336 let title = Karakeep.bookmark_title b in
306337 let status =
307307- (if b.archived then "[A]" else "")
308308- ^ if b.favourited then "[*]" else ""
338338+ (if b.archived then "[A]" else "") ^ if b.favourited then "[*]" else ""
309339 in
310340 Printf.printf "%s %s %s\n" b.id title status
311341 | Json -> print_endline (json_of_bookmark b)
···331361 match fmt with
332362 | Text ->
333363 let type_str =
334334- match l.list_type with Karakeep.Manual -> "" | Karakeep.Smart -> "[smart]"
364364+ match l.list_type with
365365+ | Karakeep.Manual -> ""
366366+ | Karakeep.Smart -> "[smart]"
335367 in
336368 Printf.printf "%s %s %s %s\n" l.id l.icon l.name type_str
337369 | Json -> print_endline (json_of_list l)
···382414(* Error handling *)
383415let handle_errors f =
384416 try f () with
385385- | Eio.Io (Karakeep.E err, _) ->
417417+ | Eio.Io (Karakeep.E err, _) -> (
386418 Logs.err (fun m -> m "Karakeep error: %s" (Karakeep.error_to_string err));
387387- (match err with
419419+ match err with
388420 | Karakeep.Api_error { status; _ } when status = 404 -> 2
389421 | Karakeep.Api_error { status; _ } when status >= 400 && status < 500 -> 2
390422 | Karakeep.Api_error _ -> 2
+31-26
lib/cmd/karakeep_cmd.mli
···1212 {2 Basic Usage}
13131414 {[
1515- open Cmdliner
1515+ open Cmdliner
16161717- let my_command =
1818- let open Karakeep_cmd in
1919- let run config =
2020- with_client config (fun client ->
2121- let bookmarks = Karakeep.fetch_all_bookmarks client () in
2222- List.iter (fun b -> print_endline (Karakeep.bookmark_title b)) bookmarks)
2323- in
2424- Cmd.v (Cmd.info "my-command") Term.(const run $ config_term)
1717+ let my_command =
1818+ let open Karakeep_cmd in
1919+ let run config =
2020+ with_client config (fun client ->
2121+ let bookmarks = Karakeep.fetch_all_bookmarks client () in
2222+ List.iter
2323+ (fun b -> print_endline (Karakeep.bookmark_title b))
2424+ bookmarks)
2525+ in
2626+ Cmd.v (Cmd.info "my-command") Term.(const run $ config_term)
2527 ]} *)
26282729(** {1 Configuration} *)
28302931type config = {
3032 base_url : string; (** Base URL of the Karakeep instance *)
3131- api_key : string; (** API key for authentication *)
3333+ api_key : string; (** API key for authentication *)
3234}
3335(** Configuration for connecting to a Karakeep instance. *)
34363537type config_opt
3636-(** Configuration options from CLI, not yet resolved.
3737- Use {!resolve_config} or {!with_client} with an Eio env to resolve. *)
3838+(** Configuration options from CLI, not yet resolved. Use {!resolve_config} or
3939+ {!with_client} with an Eio env to resolve. *)
38403941val config_opt_term : config_opt Cmdliner.Term.t
4042(** Cmdliner term that parses configuration options from command-line arguments.
4143 The actual credentials are resolved at runtime by {!resolve_config} or
4244 {!with_client} when given an Eio environment.
43454444- Configuration is resolved in priority order:
4545- 1. [--api-key KEY] flag
4646- 2. [KARAKEEP_API_KEY] environment variable
4747- 3. XDG profile credentials (~/.config/karakeep/profiles/...)
4848- 4. Legacy [--api-key-file FILE] (default: .karakeep-api)
4646+ Configuration is resolved in priority order: 1. [--api-key KEY] flag 2.
4747+ [KARAKEEP_API_KEY] environment variable 3. XDG profile credentials
4848+ (~/.config/karakeep/profiles/...) 4. Legacy [--api-key-file FILE] (default:
4949+ .karakeep-api)
49505051 Options:
5152 - [--profile NAME] or [-P NAME]: Select a specific profile
···146147(** {1 Output Terms} *)
147148148149type output_format =
149149- | Text (** Human-readable text output *)
150150- | Json (** JSON output *)
150150+ | Text (** Human-readable text output *)
151151+ | Json (** JSON output *)
151152 | Quiet (** Minimal output (IDs only) *)
152153153154val output_format_term : output_format Cmdliner.Term.t
···156157(** {1 Logging Setup} *)
157158158159val setup_logging : unit Cmdliner.Term.t
159159-(** Term that sets up logging based on verbosity flags.
160160- Use with [Logs_cli] and [Fmt_cli] for standard options. *)
160160+(** Term that sets up logging based on verbosity flags. Use with [Logs_cli] and
161161+ [Fmt_cli] for standard options. *)
161162162163val logs_term : Logs.level option Cmdliner.Term.t
163164(** Term for log level from [Logs_cli]. *)
···168169(** {1 Client Helpers} *)
169170170171val with_client :
171171- env:< clock : _ Eio.Time.clock ; fs : Eio.Fs.dir_ty Eio.Path.t ; net : _ Eio.Net.t ; .. > ->
172172+ env:
173173+ < clock : _ Eio.Time.clock
174174+ ; fs : Eio.Fs.dir_ty Eio.Path.t
175175+ ; net : _ Eio.Net.t
176176+ ; .. > ->
172177 sw:Eio.Switch.t ->
173178 config_opt ->
174179 (Karakeep.t -> 'a) ->
175180 'a
176176-(** [with_client ~env ~sw config_opt f] resolves configuration and runs [f]
177177- with a Karakeep client.
181181+(** [with_client ~env ~sw config_opt f] resolves configuration and runs [f] with
182182+ a Karakeep client.
178183179184 {[
180185 let run config_opt =
···222227(** {1 Error Handling} *)
223228224229val handle_errors : (unit -> int) -> int
225225-(** [handle_errors f] runs [f ()] and catches Karakeep errors,
226226- printing them to stderr and returning appropriate exit codes.
230230+(** [handle_errors f] runs [f ()] and catches Karakeep errors, printing them to
231231+ stderr and returning appropriate exit codes.
227232228233 Exit codes:
229234 - 0: Success
+27-27
lib/cmd/karakeep_config.ml
···33 SPDX-License-Identifier: ISC
44 ---------------------------------------------------------------------------*)
5566-type credentials = {
77- api_key : string;
88- base_url : string;
99-}
66+type credentials = { api_key : string; base_url : string }
107118let app_name = "karakeep"
129let default_base_url = "https://hoard.recoil.org"
···14111512(* TOML codec for credentials *)
1613let credentials_tomlt =
1717- Tomlt.(Table.(
1818- obj (fun api_key base_url -> { api_key; base_url })
1919- |> mem "api_key" string ~enc:(fun c -> c.api_key)
2020- |> mem "base_url" string ~enc:(fun c -> c.base_url) ~dec_absent:default_base_url
2121- |> finish
2222- ))
1414+ Tomlt.(
1515+ Table.(
1616+ obj (fun api_key base_url -> { api_key; base_url })
1717+ |> mem "api_key" string ~enc:(fun c -> c.api_key)
1818+ |> mem "base_url" string
1919+ ~enc:(fun c -> c.base_url)
2020+ ~dec_absent:default_base_url
2121+ |> finish))
23222423(* App config stores current profile name *)
2524type app_config = { current_profile : string }
26252726let app_config_tomlt =
2828- Tomlt.(Table.(
2929- obj (fun current_profile -> { current_profile })
3030- |> mem "current_profile" string ~enc:(fun c -> c.current_profile) ~dec_absent:default_profile
3131- |> finish
3232- ))
2727+ Tomlt.(
2828+ Table.(
2929+ obj (fun current_profile -> { current_profile })
3030+ |> mem "current_profile" string
3131+ ~enc:(fun c -> c.current_profile)
3232+ ~dec_absent:default_profile
3333+ |> finish))
33343435(* Directory helpers *)
3536···5960(* Config file paths *)
60616162let app_config_file fs = Eio.Path.(base_config_dir fs / "config.toml")
6262-let credentials_file fs profile = Eio.Path.(profile_dir fs profile / "credentials.toml")
6363+6464+let credentials_file fs profile =
6565+ Eio.Path.(profile_dir fs profile / "credentials.toml")
63666467(* App config operations *)
6568···102105(* Credential operations *)
103106104107let load_credentials fs ?profile () =
105105- let profile = match profile with
106106- | Some p -> p
107107- | None -> get_current_profile fs
108108+ let profile =
109109+ match profile with Some p -> p | None -> get_current_profile fs
108110 in
109111 let path = credentials_file fs profile in
110112 try
···114116 with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> None
115117116118let save_credentials fs ?profile creds =
117117- let profile = match profile with
118118- | Some p -> p
119119- | None -> get_current_profile fs
119119+ let profile =
120120+ match profile with Some p -> p | None -> get_current_profile fs
120121 in
121122 let path = credentials_file fs profile in
122123 Tomlt_eio.encode_file credentials_tomlt creds path
123124124125let clear_credentials fs ?profile () =
125125- let profile = match profile with
126126- | Some p -> p
127127- | None -> get_current_profile fs
126126+ let profile =
127127+ match profile with Some p -> p | None -> get_current_profile fs
128128 in
129129 let path = credentials_file fs profile in
130130 try Eio.Path.unlink path
···145145 match Sys.getenv_opt "KARAKEEP_API_KEY" with
146146 | Some key when key <> "" -> Some key
147147 | _ ->
148148- (* Then try .karakeep-api file *)
149149- read_api_key_file ".karakeep-api"
148148+ (* Then try .karakeep-api file *)
149149+ read_api_key_file ".karakeep-api"
+25-26
lib/cmd/karakeep_config.mli
···5566(** Karakeep configuration management with XDG support.
7788- This module provides profile-based credential storage following XDG
99- Base Directory conventions. Configuration is stored in TOML format at:
88+ This module provides profile-based credential storage following XDG Base
99+ Directory conventions. Configuration is stored in TOML format at:
10101111 {v
1212 ~/.config/karakeep/
···1616 │ └── credentials.toml
1717 └── work/
1818 └── credentials.toml
1919- v}
2020-*)
1919+ v} *)
21202221(** {1 Configuration Types} *)
23222424-type credentials = {
2525- api_key : string;
2626- base_url : string;
2727-}
2323+type credentials = { api_key : string; base_url : string }
2824(** Stored credentials for a Karakeep instance. *)
29253026(** {1 Constants} *)
···4137(** {1 Directory Paths} *)
42384339val base_config_dir : Eio.Fs.dir_ty Eio.Path.t -> Eio.Fs.dir_ty Eio.Path.t
4444-(** [base_config_dir fs] returns the base config directory for karakeep.
4545- Creates the directory if it doesn't exist. *)
4040+(** [base_config_dir fs] returns the base config directory for karakeep. Creates
4141+ the directory if it doesn't exist. *)
46424743val profiles_dir : Eio.Fs.dir_ty Eio.Path.t -> Eio.Fs.dir_ty Eio.Path.t
4848-(** [profiles_dir fs] returns the profiles subdirectory.
4949- Creates the directory if it doesn't exist. *)
4444+(** [profiles_dir fs] returns the profiles subdirectory. Creates the directory
4545+ if it doesn't exist. *)
50465147val profile_dir : Eio.Fs.dir_ty Eio.Path.t -> string -> Eio.Fs.dir_ty Eio.Path.t
5248(** [profile_dir fs profile] returns the directory for a specific profile.
···5551(** {1 Profile Management} *)
56525753val get_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string
5858-(** [get_current_profile fs] returns the current profile name.
5959- Returns ["default"] if no profile is set. *)
5454+(** [get_current_profile fs] returns the current profile name. Returns
5555+ ["default"] if no profile is set. *)
60566157val set_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string -> unit
6258(** [set_current_profile fs name] sets the current profile. *)
···66626763(** {1 Credential Storage} *)
68646969-val load_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> credentials option
7070-(** [load_credentials fs ?profile ()] loads credentials for a profile.
7171- Uses current profile if not specified. Returns [None] if not found. *)
6565+val load_credentials :
6666+ Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> credentials option
6767+(** [load_credentials fs ?profile ()] loads credentials for a profile. Uses
6868+ current profile if not specified. Returns [None] if not found. *)
72697373-val save_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> credentials -> unit
7474-(** [save_credentials fs ?profile creds] saves credentials to a profile.
7575- Uses current profile if not specified. *)
7070+val save_credentials :
7171+ Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> credentials -> unit
7272+(** [save_credentials fs ?profile creds] saves credentials to a profile. Uses
7373+ current profile if not specified. *)
76747777-val clear_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> unit
7878-(** [clear_credentials fs ?profile ()] removes credentials for a profile.
7979- Uses current profile if not specified. *)
7575+val clear_credentials :
7676+ Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> unit
7777+(** [clear_credentials fs ?profile ()] removes credentials for a profile. Uses
7878+ current profile if not specified. *)
80798180(** {1 Legacy Migration} *)
82818382val load_legacy_api_key : unit -> string option
8484-(** [load_legacy_api_key ()] attempts to read API key from legacy locations:
8585- 1. KARAKEEP_API_KEY environment variable
8686- 2. .karakeep-api file in current directory *)
8383+(** [load_legacy_api_key ()] attempts to read API key from legacy locations: 1.
8484+ KARAKEEP_API_KEY environment variable 2. .karakeep-api file in current
8585+ directory *)
···43434444 Eio_main.run @@ fun env ->
4545 Eio.Switch.run @@ fun sw ->
4646-4746 let client = Karakeep.create ~sw ~env ~base_url ~api_key in
48474948 Printf.printf "=== Test: search_bookmarks ===\n";
···53525453 Printf.printf "Searching for bookmarks with query: \"%s\"\n\n" search_term;
55545656- (try
5757- (* Search for bookmarks with the search term *)
5858- let search_results = search_bookmarks client ~query:search_term ~limit:3 () in
5555+ try
5656+ (* Search for bookmarks with the search term *)
5757+ let search_results =
5858+ search_bookmarks client ~query:search_term ~limit:3 ()
5959+ in
59606060- Printf.printf "Found %d matching bookmarks\n" (List.length search_results.bookmarks);
6161- Printf.printf "Next cursor: %s\n\n"
6262- (match search_results.next_cursor with Some c -> c | None -> "none");
6161+ Printf.printf "Found %d matching bookmarks\n"
6262+ (List.length search_results.bookmarks);
6363+ Printf.printf "Next cursor: %s\n\n"
6464+ (match search_results.next_cursor with Some c -> c | None -> "none");
63656464- (* Display the search results *)
6565- List.iter print_bookmark search_results.bookmarks;
6666+ (* Display the search results *)
6767+ List.iter print_bookmark search_results.bookmarks;
66686767- (* Test pagination if we have a next page *)
6868- (match search_results.next_cursor with
6969- | Some cursor ->
7070- Printf.printf "=== Testing search pagination ===\n";
7171- Printf.printf "Fetching next page with cursor: %s\n\n" cursor;
6969+ (* Test pagination if we have a next page *)
7070+ match search_results.next_cursor with
7171+ | Some cursor ->
7272+ Printf.printf "=== Testing search pagination ===\n";
7373+ Printf.printf "Fetching next page with cursor: %s\n\n" cursor;
72747373- let next_page = search_bookmarks client ~query:search_term ~limit:3 ~cursor () in
7575+ let next_page =
7676+ search_bookmarks client ~query:search_term ~limit:3 ~cursor ()
7777+ in
74787575- Printf.printf "Found %d more bookmarks on next page\n\n"
7676- (List.length next_page.bookmarks);
7979+ Printf.printf "Found %d more bookmarks on next page\n\n"
8080+ (List.length next_page.bookmarks);
77817878- List.iter print_bookmark next_page.bookmarks
7979- | None ->
8080- Printf.printf "No more pages available for this search query.\n")
8181- with e ->
8282- Printf.printf "An error occurred while searching: %s\n" (Printexc.to_string e);
8383- Printf.printf "\nFalling back to testing with a simple search term: \"ocaml\"\n\n";
8282+ List.iter print_bookmark next_page.bookmarks
8383+ | None -> Printf.printf "No more pages available for this search query.\n"
8484+ with e -> (
8585+ Printf.printf "An error occurred while searching: %s\n"
8686+ (Printexc.to_string e);
8787+ Printf.printf
8888+ "\nFalling back to testing with a simple search term: \"ocaml\"\n\n";
84898585- try
8686- (* Try again with a simple, reliable search term *)
8787- let search_results = search_bookmarks client ~query:"ocaml" ~limit:3 () in
9090+ try
9191+ (* Try again with a simple, reliable search term *)
9292+ let search_results = search_bookmarks client ~query:"ocaml" ~limit:3 () in
88938989- Printf.printf "Found %d matching bookmarks\n" (List.length search_results.bookmarks);
9090- Printf.printf "Next cursor: %s\n\n"
9191- (match search_results.next_cursor with Some c -> c | None -> "none");
9494+ Printf.printf "Found %d matching bookmarks\n"
9595+ (List.length search_results.bookmarks);
9696+ Printf.printf "Next cursor: %s\n\n"
9797+ (match search_results.next_cursor with Some c -> c | None -> "none");
92989393- (* Display the search results *)
9494- List.iter print_bookmark search_results.bookmarks
9595- with e ->
9696- Printf.printf "Fallback search also failed: %s\n" (Printexc.to_string e))
9999+ (* Display the search results *)
100100+ List.iter print_bookmark search_results.bookmarks
101101+ with e ->
102102+ Printf.printf "Fallback search also failed: %s\n" (Printexc.to_string e))
+36-35
test/test.ml
···43434444 Eio_main.run @@ fun env ->
4545 Eio.Switch.run @@ fun sw ->
4646-4746 let client = Karakeep.create ~sw ~env ~base_url ~api_key in
48474948 (* Test 1: fetch_bookmarks - get a single page with pagination info *)
5049 Printf.printf "=== Test 1: fetch_bookmarks (paginated) ===\n";
5151- (try
5252- let response = fetch_bookmarks client ~limit:3 () in
5353- Printf.printf "Found bookmarks, showing %d (page 1)\n"
5454- (List.length response.bookmarks);
5555- Printf.printf "Next cursor: %s\n\n"
5656- (match response.next_cursor with Some c -> c | None -> "none");
5757- List.iter print_bookmark response.bookmarks;
5050+ try
5151+ let response = fetch_bookmarks client ~limit:3 () in
5252+ Printf.printf "Found bookmarks, showing %d (page 1)\n"
5353+ (List.length response.bookmarks);
5454+ Printf.printf "Next cursor: %s\n\n"
5555+ (match response.next_cursor with Some c -> c | None -> "none");
5656+ List.iter print_bookmark response.bookmarks;
58575959- (* Test 2: fetch_all_bookmarks - get multiple pages automatically *)
6060- Printf.printf "=== Test 2: fetch_all_bookmarks (with limit) ===\n";
6161- let all_bookmarks = fetch_all_bookmarks client ~page_size:2 ~max_pages:2 () in
6262- Printf.printf "Fetched %d bookmarks from up to 2 pages\n\n"
6363- (List.length all_bookmarks);
5858+ (* Test 2: fetch_all_bookmarks - get multiple pages automatically *)
5959+ Printf.printf "=== Test 2: fetch_all_bookmarks (with limit) ===\n";
6060+ let all_bookmarks =
6161+ fetch_all_bookmarks client ~page_size:2 ~max_pages:2 ()
6262+ in
6363+ Printf.printf "Fetched %d bookmarks from up to 2 pages\n\n"
6464+ (List.length all_bookmarks);
64656565- List.iter print_bookmark
6666- (List.fold_left
6767- (fun acc x -> if List.length acc < 4 then acc @ [ x ] else acc)
6868- [] all_bookmarks);
6969- Printf.printf "... and %d more bookmarks\n\n"
7070- (max 0 (List.length all_bookmarks - 4));
6666+ List.iter print_bookmark
6767+ (List.fold_left
6868+ (fun acc x -> if List.length acc < 4 then acc @ [ x ] else acc)
6969+ [] all_bookmarks);
7070+ Printf.printf "... and %d more bookmarks\n\n"
7171+ (max 0 (List.length all_bookmarks - 4));
71727272- (* Test 3: fetch_bookmark_details - get a specific bookmark *)
7373- (match response.bookmarks with
7474- | first_bookmark :: _ ->
7575- Printf.printf "=== Test 3: fetch_bookmark_details ===\n";
7676- Printf.printf "Fetching details for bookmark ID: %s\n\n"
7777- first_bookmark.id;
7373+ (* Test 3: fetch_bookmark_details - get a specific bookmark *)
7474+ match response.bookmarks with
7575+ | first_bookmark :: _ -> (
7676+ Printf.printf "=== Test 3: fetch_bookmark_details ===\n";
7777+ Printf.printf "Fetching details for bookmark ID: %s\n\n"
7878+ first_bookmark.id;
78797979- (try
8080- let bookmark = fetch_bookmark_details client first_bookmark.id in
8181- print_bookmark bookmark
8282- with e ->
8383- Printf.printf "Error fetching bookmark details: %s\n" (Printexc.to_string e))
8484- | [] ->
8585- Printf.printf "No bookmarks found to test fetch_bookmark_details\n")
8686- with e ->
8787- Printf.printf "Error in basic tests: %s\n" (Printexc.to_string e);
8888- Printf.printf "Skipping remaining tests due to API error.\n")
8080+ try
8181+ let bookmark = fetch_bookmark_details client first_bookmark.id in
8282+ print_bookmark bookmark
8383+ with e ->
8484+ Printf.printf "Error fetching bookmark details: %s\n"
8585+ (Printexc.to_string e))
8686+ | [] -> Printf.printf "No bookmarks found to test fetch_bookmark_details\n"
8787+ with e ->
8888+ Printf.printf "Error in basic tests: %s\n" (Printexc.to_string e);
8989+ Printf.printf "Skipping remaining tests due to API error.\n"