Skip to content

Commit 4d8ac9e

Browse files
authored
Merge pull request DeusData#1802 from rarepops/fix/install-clients-registry-1798
fix(cli): apply client selector to registry integrations (DeusData#1798)
2 parents c895513 + 1e25d96 commit 4d8ac9e

3 files changed

Lines changed: 91 additions & 14 deletions

File tree

‎src/cli/cli.c‎

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7464,6 +7464,12 @@ static const char *detect_arch(void) {
74647464

74657465
/* ── Agent config install/refresh (shared by install + update) ── */
74667466

7467+
/* Set by `install --clients=...` after validation and consumed by both the
7468+
* legacy detector and the registry-backed client path (#1558, #1798). */
7469+
static const char *g_client_selection = NULL;
7470+
static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t *detected);
7471+
static bool cli_clients_selects_registry_client(const char *spec, cbm_agent_client_id_t client_id);
7472+
static void cli_clients_print_list(FILE *out);
74677473
static void print_detected_registry_agents(const char *home, bool *any);
74687474

74697475
/* Print detected agent names on a single line. */
@@ -8285,7 +8291,10 @@ static void print_detected_registry_agents(const char *home, bool *any) {
82858291
cbm_init_agent_registry_context(home, &registry);
82868292
for (size_t index = 0U; index < cbm_agent_client_count(); index++) {
82878293
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(index);
8288-
if (profile && cbm_agent_client_detect(profile->id, &registry.options)) {
8294+
if (profile &&
8295+
(!g_client_selection ||
8296+
cli_clients_selects_registry_client(g_client_selection, profile->id)) &&
8297+
cbm_agent_client_detect(profile->id, &registry.options)) {
82898298
printf(" %s", profile->display_name);
82908299
*any = true;
82918300
}
@@ -8671,7 +8680,10 @@ static void install_agent_client_registry(const char *home, const char *binary_p
86718680
cbm_init_agent_registry_context(home, &registry);
86728681
for (size_t index = 0U; index < cbm_agent_client_count(); index++) {
86738682
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(index);
8674-
if (!profile || !cbm_agent_client_detect(profile->id, &registry.options)) {
8683+
if (!profile ||
8684+
(g_client_selection &&
8685+
!cli_clients_selects_registry_client(g_client_selection, profile->id)) ||
8686+
!cbm_agent_client_detect(profile->id, &registry.options)) {
86758687
continue;
86768688
}
86778689
if (!g_install_plan) {
@@ -9545,13 +9557,6 @@ static void install_additional_agent_configs(const cbm_detected_agents_t *agents
95459557
}
95469558
}
95479559

9548-
/* #1558: set by `install --clients=...` after validation, consumed at the one
9549-
* place detection happens. Validation runs in cbm_cmd_install so an unknown
9550-
* token fails before anything is written, not midway through configuring. */
9551-
static const char *g_client_selection = NULL;
9552-
static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t *detected);
9553-
static void cli_clients_print_list(FILE *out);
9554-
95559560
int cbm_install_agent_configs(const char *home, const char *binary_path, bool force, bool dry_run) {
95569561
g_agent_install_errors = 0;
95579562
cbm_detected_agents_t agents = cbm_detect_agents(home);
@@ -9679,9 +9684,9 @@ int cbm_install_handle_existing_indexes(const char *home, bool reset, bool dry_r
96799684
* Codex had to revert the OpenCode and Cursor integrations by hand, and the
96809685
* next install silently recreated them. `--clients=claude,codex` restricts it.
96819686
*
9682-
* The table is the flag's vocabulary AND its documentation: 26 clients ship
9683-
* here, with tokens nobody would guess (factory-droid, mistral-vibe,
9684-
* copilot-cli), so `--clients=help` prints every token. A selector whose
9687+
* The tables are the flag's vocabulary AND its documentation, with tokens
9688+
* nobody would guess (factory-droid, mistral-vibe, copilot-cli), so
9689+
* `--clients=help` prints every token. A selector whose
96859690
* accepted values can only be learned from the source is not a usable
96869691
* selector, and an unrecognised token fails loudly with the list rather than
96879692
* silently configuring nothing. */
@@ -9730,10 +9735,40 @@ static void cli_clients_print_list(FILE *out) {
97309735
for (size_t i = 0; i < CLI_CLIENT_COUNT; i++) {
97319736
(void)fprintf(out, " %-16s %s\n", CLI_CLIENTS[i].token, CLI_CLIENTS[i].display);
97329737
}
9738+
for (size_t i = 0; i < cbm_agent_client_count(); i++) {
9739+
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(i);
9740+
if (profile) {
9741+
(void)fprintf(out, " %-16s %s\n", profile->stable_id, profile->display_name);
9742+
}
9743+
}
97339744
(void)fprintf(out, "\nExample: --clients=claude,codex\n"
97349745
"Omit --clients to configure every detected client.\n");
97359746
}
97369747

9748+
static bool cli_clients_spec_contains(const char *spec, const char *wanted_token) {
9749+
char buf[CLI_BUF_1K];
9750+
snprintf(buf, sizeof(buf), "%s", spec ? spec : "");
9751+
char *save = NULL;
9752+
for (char *tok = strtok_r(buf, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
9753+
while (*tok == ' ') {
9754+
tok++;
9755+
}
9756+
size_t len = strlen(tok);
9757+
while (len > 0 && tok[len - 1] == ' ') {
9758+
tok[--len] = '\0';
9759+
}
9760+
if (strcmp(tok, wanted_token) == 0) {
9761+
return true;
9762+
}
9763+
}
9764+
return false;
9765+
}
9766+
9767+
static bool cli_clients_selects_registry_client(const char *spec, cbm_agent_client_id_t client_id) {
9768+
const cbm_agent_client_profile_t *profile = cbm_agent_client_by_id(client_id);
9769+
return profile && cli_clients_spec_contains(spec, profile->stable_id);
9770+
}
9771+
97379772
/* Restrict `detected` to the comma-separated token list. Returns false (after
97389773
* printing the vocabulary) when a token is unknown, so a typo can never be
97399774
* mistaken for "that client was not installed". */
@@ -9762,6 +9797,9 @@ static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t
97629797
break;
97639798
}
97649799
}
9800+
if (!matched) {
9801+
matched = cbm_agent_client_by_stable_id(tok) != NULL;
9802+
}
97659803
if (!matched) {
97669804
(void)fprintf(stderr, "error: unknown client: %s\n\n", tok);
97679805
cli_clients_print_list(stderr);
@@ -9787,6 +9825,9 @@ size_t cbm_cli_clients_count_for_testing(void) {
97879825
const char *cbm_cli_clients_token_for_testing(size_t index) {
97889826
return index < CLI_CLIENT_COUNT ? CLI_CLIENTS[index].token : NULL;
97899827
}
9828+
void cbm_cli_set_client_selection_for_testing(const char *spec) {
9829+
g_client_selection = spec;
9830+
}
97909831
#endif
97919832

97929833
/* ── Subcommand: install ──────────────────────────────────────── */
@@ -10204,7 +10245,7 @@ int cbm_cmd_install(int argc, char **argv) {
1020410245
}
1020510246
} else if (strcmp(argv[i], "--clients") == 0) {
1020610247
/* Bare `--clients` (and `--clients=help`/`list`) print the
10207-
* vocabulary. 26 clients ship with tokens nobody would guess. */
10248+
* vocabulary, including tokens users would not readily guess. */
1020810249
cli_clients_print_list(stdout);
1020910250
return 0;
1021010251
} else if (strcmp(argv[i], "--skip-binary") == 0) {

‎src/cli/cli.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ int cbm_install_agent_configs(const char *home, const char *binary_path, bool fo
209209
bool cbm_cli_clients_apply_selection_for_testing(const char *spec, cbm_detected_agents_t *detected);
210210
size_t cbm_cli_clients_count_for_testing(void);
211211
const char *cbm_cli_clients_token_for_testing(size_t index);
212+
void cbm_cli_set_client_selection_for_testing(const char *spec);
212213
#endif
213214

214215
#ifdef CBM_CLI_ENABLE_TEST_API

‎tests/test_cli.c‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13492,7 +13492,7 @@ TEST(cli_external_manager_detection_needs_positive_evidence_issue1566) {
1349213492
* Claude and Codex had to revert OpenCode and Cursor by hand — and the next
1349313493
* install silently recreated them. The selector restricts it.
1349413494
*
13495-
* The vocabulary is the part that makes it usable: 26 clients ship, with tokens
13495+
* The vocabulary is the part that makes it usable: clients ship with tokens
1349613496
* nobody would guess (factory-droid, mistral-vibe, copilot-cli). A selector
1349713497
* whose accepted values can only be learned by reading our source is not a
1349813498
* usable selector, so this pins that every client is listed and that an unknown
@@ -13521,6 +13521,12 @@ TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558) {
1352113521
cbm_detected_agents_t typo = all;
1352213522
ASSERT_FALSE(cbm_cli_clients_apply_selection_for_testing("claude,codx", &typo));
1352313523

13524+
/* Registry-backed clients share the public selector vocabulary. */
13525+
cbm_detected_agents_t registry = all;
13526+
ASSERT_TRUE(cbm_cli_clients_apply_selection_for_testing("qoder", &registry));
13527+
ASSERT_FALSE(registry.claude_code);
13528+
ASSERT_FALSE(registry.cursor);
13529+
1352413530
/* Every token in the table must resolve — a client added to detection but
1352513531
* forgotten here is invisible to the selector. */
1352613532
for (size_t i = 0; i < cbm_cli_clients_count_for_testing(); i++) {
@@ -13532,6 +13538,34 @@ TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558) {
1353213538
PASS();
1353313539
}
1353413540

13541+
TEST(cli_clients_selector_filters_registry_installs_issue1798) {
13542+
char *tmpdir = th_mktempdir("cbm_cli_clients_registry");
13543+
ASSERT_NOT_NULL(tmpdir);
13544+
13545+
char qoder_dir[512];
13546+
char settings_path[512];
13547+
ASSERT(snprintf(qoder_dir, sizeof(qoder_dir), "%s/.qoder", tmpdir) > 0);
13548+
ASSERT(snprintf(settings_path, sizeof(settings_path), "%s/settings.json", qoder_dir) > 0);
13549+
ASSERT_EQ(test_mkdirp(qoder_dir), 0);
13550+
13551+
cbm_cli_set_client_selection_for_testing("cursor");
13552+
int excluded_rc = cbm_install_agent_configs(tmpdir, "/opt/codebase-memory-mcp", false, false);
13553+
struct stat settings_state;
13554+
bool excluded = stat(settings_path, &settings_state) != 0;
13555+
13556+
cbm_cli_set_client_selection_for_testing("qoder");
13557+
int included_rc = cbm_install_agent_configs(tmpdir, "/opt/codebase-memory-mcp", false, false);
13558+
bool included = stat(settings_path, &settings_state) == 0;
13559+
cbm_cli_set_client_selection_for_testing(NULL);
13560+
13561+
test_rmdir_r(tmpdir);
13562+
ASSERT_EQ(excluded_rc, 0);
13563+
ASSERT_TRUE(excluded);
13564+
ASSERT_EQ(included_rc, 0);
13565+
ASSERT_TRUE(included);
13566+
PASS();
13567+
}
13568+
1353513569
TEST(cli_update_accepts_retired_variant_flags_issue1544) {
1353613570
char *ui_argv[] = {"--dry-run", "--ui", "--yes"};
1353713571
char *standard_argv[] = {"--dry-run", "--standard", "--yes"};
@@ -13679,6 +13713,7 @@ SUITE(cli) {
1367913713
RUN_TEST(cli_skill_frontmatter_scalars_with_colons_are_quoted_issue1554);
1368013714
RUN_TEST(cli_external_manager_detection_needs_positive_evidence_issue1566);
1368113715
RUN_TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558);
13716+
RUN_TEST(cli_clients_selector_filters_registry_installs_issue1798);
1368213717
RUN_TEST(cli_update_accepts_retired_variant_flags_issue1544);
1368313718
RUN_TEST(cli_update_agent_configs_finish_before_guard_release);
1368413719
RUN_TEST(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index);

0 commit comments

Comments
 (0)