My agentic slop goes here. Not intended for anyone else!
1(** Toru Cache Management CLI Tool *)
2
3open Cmdliner
4
5module File_info = struct
6 type t = {
7 name : string;
8 size : int64;
9 mtime : Ptime.t;
10 path : string;
11 }
12
13 let create ~name ~size ~mtime ~path =
14 { name; size; mtime; path }
15
16 let compare_by_size a b = Int64.compare b.size a.size (* Largest first *)
17 let compare_by_age a b = Ptime.compare a.mtime b.mtime (* Oldest first *)
18 let compare_by_name a b = String.compare a.name b.name
19end
20
21module Utils = struct
22 let human_readable_bytes bytes =
23 let units = [|"B"; "KB"; "MB"; "GB"; "TB"|] in
24 let rec loop bytes unit_index =
25 if bytes < 1024.0 || unit_index >= Array.length units - 1 then
26 Printf.sprintf "%.1f %s" bytes units.(unit_index)
27 else
28 loop (bytes /. 1024.0) (unit_index + 1)
29 in
30 loop (Int64.to_float bytes) 0
31
32 let format_time_ago ptime =
33 let now = Ptime_clock.now () in
34 let span = Ptime.diff now ptime in
35 let days = Ptime.Span.to_d_ps span |> fst in
36 if days = 0 then "today"
37 else if days = 1 then "1 day ago"
38 else Printf.sprintf "%d days ago" days
39
40
41 let get_file_info cache_path filename =
42 let full_path = Eio.Path.(cache_path / filename) in
43 try
44 let stat = Eio.Path.stat ~follow:false full_path in
45 let mtime = Ptime.of_float_s stat.mtime |> Option.value ~default:(Ptime_clock.now ())
46 in
47 Some (File_info.create
48 ~name:filename
49 ~size:(Optint.Int63.to_int64 stat.size)
50 ~mtime
51 ~path:(Eio.Path.native_exn full_path))
52 with
53 | _ -> None
54
55 let collect_file_info cache =
56 let cache_path = match Toru.Cache.version cache with
57 | None -> Toru.Cache.base_path cache
58 | Some v -> Eio.Path.(Toru.Cache.base_path cache / v)
59 in
60 let filenames = Toru.Cache.list_files cache in
61 List.filter_map (get_file_info cache_path) filenames
62
63 let print_header title =
64 Fmt.(pf stdout "%a@." (styled `Bold (styled `Cyan string)) title);
65 Fmt.(pf stdout "%a@." (styled `Cyan string) (String.make (String.length title) '='))
66
67 let print_success msg =
68 Fmt.(pf stdout "%a%s@." (styled `Green string) "[OK] " msg)
69
70 let print_warning msg =
71 Fmt.(pf stdout "%a%s@." (styled `Yellow string) "[WARN] " msg)
72
73 let print_error msg =
74 Fmt.(pf stdout "%a%s@." (styled `Red string) "[ERROR] " msg)
75end
76
77(* Global options *)
78type global_opts = {
79 cache_dir : string option;
80 app_name : string;
81 version : string option;
82}
83
84let global_opts_term =
85 let cache_dir =
86 let doc = "Override default cache location" in
87 Arg.(value & opt (some string) None & info ["cache-dir"; "c"] ~docv:"DIR" ~doc)
88 in
89 let app_name =
90 let doc = "Override application name (default: toru)" in
91 Arg.(value & opt string "toru" & info ["app-name"] ~docv:"NAME" ~doc)
92 in
93 let version =
94 let doc = "Target specific cache version" in
95 Arg.(value & opt (some string) None & info ["cache-version"; "v"] ~docv:"VER" ~doc)
96 in
97 Term.(const (fun cache_dir app_name version -> { cache_dir; app_name; version })
98 $ cache_dir $ app_name $ version)
99
100(* Command implementations *)
101let info_cmd env sw global_opts =
102 let cache = match global_opts.cache_dir with
103 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version path
104 | None ->
105 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
106 Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version base_path
107 in
108
109 let cache = match global_opts.version with
110 | Some v ->
111 (* Create new cache with version override *)
112 (match global_opts.cache_dir with
113 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ~version:v path
114 | None ->
115 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
116 Toru.Cache.create ~sw ~fs:env#fs ~version:v base_path)
117 | None -> cache
118 in
119
120 Utils.print_header "Toru Cache Information";
121
122 let cache_path = Toru.Cache.base_path cache in
123 let version_path = match Toru.Cache.version cache with
124 | Some v -> Eio.Path.(cache_path / v)
125 | None -> cache_path
126 in
127
128 Printf.printf "Location: %s\n" (Eio.Path.native_exn version_path);
129
130 (match Toru.Cache.version cache with
131 | Some v -> Printf.printf "Version: %s\n" v
132 | None -> Printf.printf "Version: none\n");
133
134 let total_size = Toru.Cache.size_bytes cache in
135 Printf.printf "Total Size: %s (%Ld bytes)\n"
136 (Utils.human_readable_bytes total_size) total_size;
137
138 let file_count = List.length (Toru.Cache.list_files cache) in
139 Printf.printf "File Count: %d files\n" file_count;
140
141 if file_count > 0 then (
142 let file_infos = Utils.collect_file_info cache in
143 match file_infos with
144 | [] -> Printf.printf "Age Range: No files found\n"
145 | files ->
146 let sorted_by_age = List.sort File_info.compare_by_age files in
147 let oldest = List.hd sorted_by_age in
148 let newest = List.hd (List.rev sorted_by_age) in
149 Printf.printf "Age Range: %s to %s\n"
150 (Utils.format_time_ago oldest.mtime)
151 (Utils.format_time_ago newest.mtime)
152 );
153
154 Printf.printf "Free Space: Unable to determine\n";
155 0
156
157let list_cmd env sw global_opts sort_by format limit =
158 let cache = match global_opts.cache_dir with
159 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version path
160 | None ->
161 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
162 Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version base_path
163 in
164
165 let cache = match global_opts.version with
166 | Some v ->
167 (match global_opts.cache_dir with
168 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ~version:v path
169 | None ->
170 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
171 Toru.Cache.create ~sw ~fs:env#fs ~version:v base_path)
172 | None -> cache
173 in
174
175 let file_infos = Utils.collect_file_info cache in
176
177 let sorted_files = match sort_by with
178 | `Size -> List.sort File_info.compare_by_size file_infos
179 | `Age -> List.sort File_info.compare_by_age file_infos
180 | `Name -> List.sort File_info.compare_by_name file_infos
181 in
182
183 let limited_files = match limit with
184 | Some n ->
185 let rec take n lst acc =
186 match n, lst with
187 | 0, _ | _, [] -> List.rev acc
188 | n, x :: xs -> take (n - 1) xs (x :: acc)
189 in
190 take n sorted_files []
191 | None -> sorted_files
192 in
193
194 (match format with
195 | `Table ->
196 if limited_files = [] then
197 Printf.printf "No files found in cache.\n"
198 else (
199 Printf.printf "%-50s %12s %12s %s\n" "Filename" "Size" "Age" "Hash (SHA256)";
200 Fmt.(pf stdout "%a@." (styled `Cyan string) (String.make 90 '-'));
201
202 List.iter (fun file ->
203 let truncated_name =
204 if String.length file.File_info.name > 47 then
205 String.sub file.File_info.name 0 44 ^ "..."
206 else file.File_info.name
207 in
208 Printf.printf "%-50s %12s %12s %s\n"
209 truncated_name
210 (Utils.human_readable_bytes file.File_info.size)
211 (Utils.format_time_ago file.File_info.mtime)
212 "no hash" (* TODO: Add hash computation if needed *)
213 ) limited_files
214 )
215 | `Json ->
216 let json_files = List.map (fun file ->
217 `Assoc [
218 ("filename", `String file.File_info.name);
219 ("size", `Int (Int64.to_int file.File_info.size));
220 ("path", `String file.File_info.path);
221 ("mtime", `String (Ptime.to_rfc3339 file.File_info.mtime));
222 ("age_days", `Int (
223 let span = Ptime.diff (Ptime_clock.now ()) file.File_info.mtime in
224 Ptime.Span.to_d_ps span |> fst
225 ));
226 ]
227 ) limited_files in
228 let json_output = `List json_files in
229 Printf.printf "%s\n" (Yojson.Safe.pretty_to_string json_output)
230 );
231 0
232
233let size_cmd env sw global_opts breakdown human_readable =
234 let cache = match global_opts.cache_dir with
235 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version path
236 | None ->
237 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
238 Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version base_path
239 in
240
241 let cache = match global_opts.version with
242 | Some v ->
243 (match global_opts.cache_dir with
244 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ~version:v path
245 | None ->
246 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
247 Toru.Cache.create ~sw ~fs:env#fs ~version:v base_path)
248 | None -> cache
249 in
250
251 let total_size = Toru.Cache.size_bytes cache in
252
253 if human_readable then
254 Printf.printf "Total Size: %s\n" (Utils.human_readable_bytes total_size)
255 else
256 Printf.printf "Total Size: %Ld bytes\n" total_size;
257
258 if breakdown then (
259 let file_infos = Utils.collect_file_info cache in
260
261 (* Breakdown by file extension *)
262 let ext_map = Hashtbl.create 16 in
263 List.iter (fun file ->
264 let ext =
265 try
266 let dot_idx = String.rindex file.File_info.name '.' in
267 String.sub file.File_info.name dot_idx (String.length file.File_info.name - dot_idx)
268 with
269 | Not_found -> "no extension"
270 in
271 let current = Hashtbl.find_opt ext_map ext |> Option.value ~default:0L in
272 Hashtbl.replace ext_map ext (Int64.add current file.File_info.size)
273 ) file_infos;
274
275 Printf.printf "\nBreakdown by file type:\n";
276 Fmt.(pf stdout "%a@." (styled `Cyan string) (String.make 40 '-'));
277
278 let ext_list = Hashtbl.fold (fun ext size acc -> (ext, size) :: acc) ext_map [] in
279 let sorted_ext = List.sort (fun (_, a) (_, b) -> Int64.compare b a) ext_list in
280
281 List.iter (fun (ext, size) ->
282 let percentage = if total_size > 0L then
283 Int64.to_float size /. Int64.to_float total_size *. 100.0
284 else 0.0 in
285 if human_readable then
286 Printf.printf "%-20s %12s (%5.1f%%)\n" ext (Utils.human_readable_bytes size) percentage
287 else
288 Printf.printf "%-20s %12Ld (%5.1f%%)\n" ext size percentage
289 ) sorted_ext;
290
291 (* Breakdown by age *)
292 let now = Ptime_clock.now () in
293 let age_buckets = [
294 ("< 1 day", 1);
295 ("1-7 days", 7);
296 ("1-4 weeks", 28);
297 ("1-12 months", 365);
298 ("> 1 year", max_int);
299 ] in
300
301 Printf.printf "\nBreakdown by age:\n";
302 Fmt.(pf stdout "%a@." (styled `Cyan string) (String.make 40 '-'));
303
304 List.iter (fun (label, max_days) ->
305 let bucket_size = List.fold_left (fun acc file ->
306 let span = Ptime.diff now file.File_info.mtime in
307 let days = Ptime.Span.to_d_ps span |> fst in
308 if days <= max_days then Int64.add acc file.File_info.size else acc
309 ) 0L file_infos in
310
311 let percentage = if total_size > 0L then
312 Int64.to_float bucket_size /. Int64.to_float total_size *. 100.0
313 else 0.0 in
314
315 if human_readable then
316 Printf.printf "%-20s %12s (%5.1f%%)\n" label (Utils.human_readable_bytes bucket_size) percentage
317 else
318 Printf.printf "%-20s %12Ld (%5.1f%%)\n" label bucket_size percentage
319 ) age_buckets
320 );
321 ();
322 0
323
324let clean_cmd env sw global_opts max_size max_age dry_run =
325 let cache = match global_opts.cache_dir with
326 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version path
327 | None ->
328 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
329 Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version base_path
330 in
331
332 let cache = match global_opts.version with
333 | Some v ->
334 (match global_opts.cache_dir with
335 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ~version:v path
336 | None ->
337 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
338 Toru.Cache.create ~sw ~fs:env#fs ~version:v base_path)
339 | None -> cache
340 in
341
342 let file_infos = Utils.collect_file_info cache in
343 let now = Ptime_clock.now () in
344
345 let files_to_remove = List.filter (fun file ->
346 (* Check age constraint *)
347 let age_match = match max_age with
348 | Some max_days ->
349 let span = Ptime.diff now file.File_info.mtime in
350 let days = Ptime.Span.to_d_ps span |> fst in
351 days > max_days
352 | None -> true
353 in
354 age_match
355 ) file_infos in
356
357 (* If max_size is specified, sort by age and remove oldest until under limit *)
358 let files_to_remove = match max_size with
359 | Some max_bytes ->
360 let current_size = List.fold_left (fun acc file -> Int64.add acc file.File_info.size) 0L file_infos in
361 if current_size <= max_bytes then
362 []
363 else
364 let sorted_by_age = List.sort File_info.compare_by_age file_infos in
365 let rec select_for_removal remaining_files target_reduction acc_size acc_files =
366 match remaining_files with
367 | [] -> acc_files
368 | file :: rest ->
369 if acc_size >= target_reduction then acc_files
370 else select_for_removal rest target_reduction
371 (Int64.add acc_size file.File_info.size) (file :: acc_files)
372 in
373 select_for_removal sorted_by_age (Int64.sub current_size max_bytes) 0L []
374 | None -> files_to_remove
375 in
376
377 if files_to_remove = [] then (
378 Utils.print_success "No files need to be removed.";
379 ()
380 ) else (
381 let total_size_to_remove = List.fold_left (fun acc file -> Int64.add acc file.File_info.size) 0L files_to_remove in
382 let file_count = List.length files_to_remove in
383
384 if dry_run then (
385 Utils.print_header "Dry Run: Cache Cleanup";
386 Printf.printf "Would remove %d files (%s)\n\n"
387 file_count (Utils.human_readable_bytes total_size_to_remove);
388
389 Printf.printf "Files to be removed:\n";
390 List.iter (fun file ->
391 Printf.printf "- %s (%s, %s)\n"
392 file.File_info.name
393 (Utils.human_readable_bytes file.File_info.size)
394 (Utils.format_time_ago file.File_info.mtime)
395 ) files_to_remove;
396
397 print_endline "";
398 Utils.print_warning "Use without --dry-run to proceed with cleanup."
399 ) else (
400 Utils.print_header "Cache Cleanup";
401 Printf.printf "Removing %d files (%s)...\n\n"
402 file_count (Utils.human_readable_bytes total_size_to_remove);
403
404 let removed_count = ref 0 in
405 List.iter (fun file ->
406 try
407 Unix.unlink file.File_info.path;
408 incr removed_count;
409 Printf.printf "Removed: %s\n" file.File_info.name
410 with
411 | exn ->
412 Utils.print_error (Printf.sprintf "Failed to remove %s: %s"
413 file.File_info.name (Printexc.to_string exn))
414 ) files_to_remove;
415
416 Printf.printf "\nRemoved %d files successfully.\n" !removed_count
417 );
418 ()
419 );
420 0
421
422let vacuum_cmd env sw global_opts dry_run =
423 let cache = match global_opts.cache_dir with
424 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version path
425 | None ->
426 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
427 Toru.Cache.create ~sw ~fs:env#fs ?version:global_opts.version base_path
428 in
429
430 let cache = match global_opts.version with
431 | Some v ->
432 (match global_opts.cache_dir with
433 | Some path -> Toru.Cache.create ~sw ~fs:env#fs ~version:v path
434 | None ->
435 let base_path = Toru.Cache.default_cache_path ~app_name:global_opts.app_name () in
436 Toru.Cache.create ~sw ~fs:env#fs ~version:v base_path)
437 | None -> cache
438 in
439
440 let cache_path = match Toru.Cache.version cache with
441 | None -> Toru.Cache.base_path cache
442 | Some v -> Eio.Path.(Toru.Cache.base_path cache / v)
443 in
444
445 let rec find_empty_dirs path =
446 try
447 let entries = Eio.Path.read_dir path in
448 if entries = [] then
449 [path]
450 else
451 List.fold_left (fun acc entry ->
452 let entry_path = Eio.Path.(path / entry) in
453 let stat = Eio.Path.stat ~follow:false entry_path in
454 match stat.kind with
455 | `Directory -> (find_empty_dirs entry_path) @ acc
456 | _ -> acc
457 ) [] entries
458 with
459 | _ -> []
460 in
461
462 let empty_dirs = find_empty_dirs cache_path in
463 let cache_path_str = Eio.Path.native_exn cache_path in
464 let empty_dirs = List.filter (fun dir -> not (String.equal (Eio.Path.native_exn dir) cache_path_str)) empty_dirs in
465
466 if empty_dirs = [] then (
467 Utils.print_success "No empty directories found.";
468 ()
469 ) else (
470 if dry_run then (
471 Utils.print_header "Dry Run: Vacuum Cache";
472 Printf.printf "Would remove %d empty directories:\n\n" (List.length empty_dirs);
473 List.iter (fun dir ->
474 Printf.printf "- %s\n" (Eio.Path.native_exn dir)
475 ) empty_dirs;
476 print_endline "";
477 Utils.print_warning "Use without --dry-run to proceed with vacuum."
478 ) else (
479 Utils.print_header "Vacuum Cache";
480 Printf.printf "Removing %d empty directories...\n\n" (List.length empty_dirs);
481
482 let removed_count = ref 0 in
483 List.iter (fun dir ->
484 try
485 Eio.Path.rmdir dir;
486 incr removed_count;
487 Printf.printf "Removed: %s\n" (Eio.Path.native_exn dir)
488 with
489 | exn ->
490 Utils.print_error (Printf.sprintf "Failed to remove %s: %s"
491 (Eio.Path.native_exn dir) (Printexc.to_string exn))
492 ) empty_dirs;
493
494 Printf.printf "\nRemoved %d directories successfully.\n" !removed_count
495 );
496 ()
497 );
498 0
499
500(* Command definitions *)
501let info_cmd_def eio_env sw =
502 let doc = "Show cache statistics and location" in
503 let term = Term.(const (info_cmd eio_env sw) $ global_opts_term) in
504 Cmd.v (Cmd.info "info" ~doc) term
505
506let list_cmd_def eio_env sw =
507 let sort_by =
508 let doc = "Sort files by size, age, or name" in
509 Arg.(value & opt (enum [("size", `Size); ("age", `Age); ("name", `Name)]) `Name &
510 info ["sort"] ~docv:"FIELD" ~doc)
511 in
512 let format =
513 let doc = "Output format: table or json" in
514 Arg.(value & opt (enum [("table", `Table); ("json", `Json)]) `Table &
515 info ["format"; "f"] ~docv:"FORMAT" ~doc)
516 in
517 let limit =
518 let doc = "Limit number of files shown" in
519 Arg.(value & opt (some int) None & info ["limit"; "n"] ~docv:"N" ~doc)
520 in
521 let doc = "List cached files with details" in
522 let term = Term.(const (list_cmd eio_env sw) $ global_opts_term $ sort_by $ format $ limit) in
523 Cmd.v (Cmd.info "list" ~doc) term
524
525let size_cmd_def eio_env sw =
526 let breakdown =
527 let doc = "Show size breakdown by file type and age" in
528 Arg.(value & flag & info ["breakdown"; "b"] ~doc)
529 in
530 let human_readable =
531 let doc = "Display sizes in human readable format" in
532 Arg.(value & flag & info ["human-readable"; "h"] ~doc)
533 in
534 let doc = "Show cache size information" in
535 let term = Term.(const (size_cmd eio_env sw) $ global_opts_term $ breakdown $ human_readable) in
536 Cmd.v (Cmd.info "size" ~doc) term
537
538let clean_cmd_def eio_env sw =
539 let max_size =
540 let doc = "Remove files to get cache under this size (e.g., 1GB, 500MB)" in
541 let parse_size s =
542 let s = String.uppercase_ascii s in
543 let len = String.length s in
544 if len < 2 then Error "Invalid size format"
545 else
546 let (num_str, unit) =
547 if String.sub s (len-2) 2 = "GB" then
548 (String.sub s 0 (len-2), Int64.(mul 1024L (mul 1024L 1024L)))
549 else if String.sub s (len-2) 2 = "MB" then
550 (String.sub s 0 (len-2), Int64.(mul 1024L 1024L))
551 else if String.sub s (len-2) 2 = "KB" then
552 (String.sub s 0 (len-2), 1024L)
553 else if String.sub s (len-1) 1 = "B" then
554 (String.sub s 0 (len-1), 1L)
555 else
556 (s, 1L)
557 in
558 try
559 let num = Float.of_string num_str in
560 Ok (Int64.of_float (num *. Int64.to_float unit))
561 with
562 | _ -> Error "Invalid number in size"
563 in
564 let size_conv = Arg.conv' (parse_size, fun fmt size ->
565 Format.fprintf fmt "%Ld" size) in
566 Arg.(value & opt (some size_conv) None & info ["max-size"] ~docv:"SIZE" ~doc)
567 in
568 let max_age =
569 let doc = "Remove files older than this many days" in
570 Arg.(value & opt (some int) None & info ["max-age"] ~docv:"DAYS" ~doc)
571 in
572 let dry_run =
573 let doc = "Show what would be removed without actually removing" in
574 Arg.(value & flag & info ["dry-run"; "n"] ~doc)
575 in
576 let doc = "Clean cache with various options" in
577 let term = Term.(const (clean_cmd eio_env sw) $ global_opts_term $ max_size $ max_age $ dry_run) in
578 Cmd.v (Cmd.info "clean" ~doc) term
579
580let vacuum_cmd_def eio_env sw =
581 let dry_run =
582 let doc = "Show what would be removed without actually removing" in
583 Arg.(value & flag & info ["dry-run"; "n"] ~doc)
584 in
585 let doc = "Remove empty directories and broken links" in
586 let term = Term.(const (vacuum_cmd eio_env sw) $ global_opts_term $ dry_run) in
587 Cmd.v (Cmd.info "vacuum" ~doc) term
588
589let main_cmd eio_env sw =
590 let doc = "Toru cache management tool" in
591 let sdocs = Manpage.s_common_options in
592 let man = [
593 `S Manpage.s_description;
594 `P "$(tname) manages the Toru data cache, providing commands to inspect, clean, and maintain cached data files.";
595 `P "The cache follows XDG Base Directory specifications on Unix systems and uses appropriate locations on other platforms.";
596 `S Manpage.s_commands;
597 `P "Use $(b,$(tname) COMMAND --help) for command-specific help.";
598 `S "ENVIRONMENT VARIABLES";
599 `P "$(b,XDG_CACHE_HOME) - Override default cache location on Unix systems";
600 `P "$(b,TORU_CACHE_DIR) - Override cache location (takes precedence)";
601 `S "EXAMPLES";
602 `P "$(b,toru-cache info) - Show cache information";
603 `P "$(b,toru-cache list --sort=size --limit=10) - Show 10 largest files";
604 `P "$(b,toru-cache clean --max-size=1GB --dry-run) - Preview cleanup to 1GB limit";
605 `P "$(b,toru-cache size --breakdown -h) - Show human-readable size breakdown";
606 ] in
607 let default = Term.(const 0) in
608 Cmd.group ~default (Cmd.info "toru-cache" ~version:"0.1.0" ~doc ~sdocs ~man)
609 [info_cmd_def eio_env sw; list_cmd_def eio_env sw; size_cmd_def eio_env sw; clean_cmd_def eio_env sw; vacuum_cmd_def eio_env sw]
610
611let () =
612 Eio_main.run @@ fun env ->
613 Eio.Switch.run @@ fun sw ->
614 exit (Cmd.eval' (main_cmd env sw))