list_projects: scan cache directory for .db files. * Each project is a single .db file — no central registry needed. */
| 2579 | /* list_projects: scan cache directory for .db files. |
| 2580 | * Each project is a single .db file — no central registry needed. */ |
| 2581 | static char *handle_list_projects(cbm_mcp_server_t *srv, const char *args) { |
| 2582 | (void)srv; |
| 2583 | int offset = cbm_mcp_get_int_arg(args, "offset", 0); |
| 2584 | int limit = cbm_mcp_get_int_arg(args, "limit", 50); |
| 2585 | bool include_details = cbm_mcp_get_bool_arg(args, "include_details"); |
| 2586 | yyjson_doc *args_doc = args ? yyjson_read(args, strlen(args), 0) : NULL; |
| 2587 | yyjson_val *args_root = args_doc ? yyjson_doc_get_root(args_doc) : NULL; |
| 2588 | yyjson_val *metadata_value = |
| 2589 | args_root && yyjson_is_obj(args_root) ? yyjson_obj_get(args_root, "metadata_only") : NULL; |
| 2590 | if (metadata_value && yyjson_is_true(metadata_value)) { |
| 2591 | include_details = false; |
| 2592 | } |
| 2593 | if (args_doc) { |
| 2594 | yyjson_doc_free(args_doc); |
| 2595 | } |
| 2596 | if (offset < 0) { |
| 2597 | offset = 0; |
| 2598 | } |
| 2599 | if (limit < 1) { |
| 2600 | limit = 1; |
| 2601 | } else if (limit > 100) { |
| 2602 | limit = 100; |
| 2603 | } |
| 2604 | |
| 2605 | char dir_path[CBM_SZ_1K]; |
| 2606 | cache_dir(dir_path, sizeof(dir_path)); |
| 2607 | |
| 2608 | cbm_dir_t *d = cbm_opendir(dir_path); |
| 2609 | |
| 2610 | yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); |
| 2611 | yyjson_mut_val *root = yyjson_mut_obj(doc); |
| 2612 | yyjson_mut_doc_set_root(doc, root); |
| 2613 | yyjson_mut_val *arr = yyjson_mut_arr(doc); |
| 2614 | |
| 2615 | if (!d) { |
| 2616 | char msg[CBM_SZ_1K]; |
| 2617 | snprintf(msg, sizeof(msg), |
| 2618 | "{\"error\":\"cannot read cache directory: %s\",\"hint\":" |
| 2619 | "\"Check directory permissions or run index_repository first.\"}", |
| 2620 | dir_path); |
| 2621 | yyjson_mut_doc_free(doc); |
| 2622 | return cbm_mcp_text_result(msg, true); |
| 2623 | } |
| 2624 | |
| 2625 | char **db_names = NULL; |
| 2626 | size_t db_count = 0; |
| 2627 | size_t db_cap = 0; |
| 2628 | bool name_collection_failed = false; |
| 2629 | cbm_dirent_t *entry; |
| 2630 | while ((entry = cbm_readdir(d)) != NULL) { |
| 2631 | const char *name = entry->name; |
| 2632 | size_t len = strlen(name); |
| 2633 | if (!is_project_db_file(name, len)) { |
| 2634 | continue; |
| 2635 | } |
| 2636 | if (db_count == db_cap) { |
| 2637 | size_t next_cap = db_cap ? db_cap * 2 : 64; |
| 2638 | char **grown = realloc(db_names, next_cap * sizeof(*db_names)); |
no test coverage detected