Skip to content

Commit 1e25d96

Browse files
committed
fix(cli): apply client selector to registry integrations (DeusData#1798)
Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
1 parent 010569f commit 1e25d96

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
@@ -7376,6 +7376,12 @@ static const char *detect_arch(void) {
73767376

73777377
/* ── Agent config install/refresh (shared by install + update) ── */
73787378

7379+
/* Set by `install --clients=...` after validation and consumed by both the
7380+
* legacy detector and the registry-backed client path (#1558, #1798). */
7381+
static const char *g_client_selection = NULL;
7382+
static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t *detected);
7383+
static bool cli_clients_selects_registry_client(const char *spec, cbm_agent_client_id_t client_id);
7384+
static void cli_clients_print_list(FILE *out);
73797385
static void print_detected_registry_agents(const char *home, bool *any);
73807386

73817387
/* Print detected agent names on a single line. */
@@ -8169,7 +8175,10 @@ static void print_detected_registry_agents(const char *home, bool *any) {
81698175
cbm_init_agent_registry_context(home, &registry);
81708176
for (size_t index = 0U; index < cbm_agent_client_count(); index++) {
81718177
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(index);
8172-
if (profile && cbm_agent_client_detect(profile->id, &registry.options)) {
8178+
if (profile &&
8179+
(!g_client_selection ||
8180+
cli_clients_selects_registry_client(g_client_selection, profile->id)) &&
8181+
cbm_agent_client_detect(profile->id, &registry.options)) {
81738182
printf(" %s", profile->display_name);
81748183
*any = true;
81758184
}
@@ -8525,7 +8534,10 @@ static void install_agent_client_registry(const char *home, const char *binary_p
85258534
cbm_init_agent_registry_context(home, &registry);
85268535
for (size_t index = 0U; index < cbm_agent_client_count(); index++) {
85278536
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(index);
8528-
if (!profile || !cbm_agent_client_detect(profile->id, &registry.options)) {
8537+
if (!profile ||
8538+
(g_client_selection &&
8539+
!cli_clients_selects_registry_client(g_client_selection, profile->id)) ||
8540+
!cbm_agent_client_detect(profile->id, &registry.options)) {
85298541
continue;
85308542
}
85318543
if (!g_install_plan) {
@@ -9365,13 +9377,6 @@ static void install_additional_agent_configs(const cbm_detected_agents_t *agents
93659377
}
93669378
}
93679379

9368-
/* #1558: set by `install --clients=...` after validation, consumed at the one
9369-
* place detection happens. Validation runs in cbm_cmd_install so an unknown
9370-
* token fails before anything is written, not midway through configuring. */
9371-
static const char *g_client_selection = NULL;
9372-
static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t *detected);
9373-
static void cli_clients_print_list(FILE *out);
9374-
93759380
int cbm_install_agent_configs(const char *home, const char *binary_path, bool force, bool dry_run) {
93769381
g_agent_install_errors = 0;
93779382
cbm_detected_agents_t agents = cbm_detect_agents(home);
@@ -9499,9 +9504,9 @@ int cbm_install_handle_existing_indexes(const char *home, bool reset, bool dry_r
94999504
* Codex had to revert the OpenCode and Cursor integrations by hand, and the
95009505
* next install silently recreated them. `--clients=claude,codex` restricts it.
95019506
*
9502-
* The table is the flag's vocabulary AND its documentation: 26 clients ship
9503-
* here, with tokens nobody would guess (factory-droid, mistral-vibe,
9504-
* copilot-cli), so `--clients=help` prints every token. A selector whose
9507+
* The tables are the flag's vocabulary AND its documentation, with tokens
9508+
* nobody would guess (factory-droid, mistral-vibe, copilot-cli), so
9509+
* `--clients=help` prints every token. A selector whose
95059510
* accepted values can only be learned from the source is not a usable
95069511
* selector, and an unrecognised token fails loudly with the list rather than
95079512
* silently configuring nothing. */
@@ -9549,10 +9554,40 @@ static void cli_clients_print_list(FILE *out) {
95499554
for (size_t i = 0; i < CLI_CLIENT_COUNT; i++) {
95509555
(void)fprintf(out, " %-16s %s\n", CLI_CLIENTS[i].token, CLI_CLIENTS[i].display);
95519556
}
9557+
for (size_t i = 0; i < cbm_agent_client_count(); i++) {
9558+
const cbm_agent_client_profile_t *profile = cbm_agent_client_at(i);
9559+
if (profile) {
9560+
(void)fprintf(out, " %-16s %s\n", profile->stable_id, profile->display_name);
9561+
}
9562+
}
95529563
(void)fprintf(out, "\nExample: --clients=claude,codex\n"
95539564
"Omit --clients to configure every detected client.\n");
95549565
}
95559566

9567+
static bool cli_clients_spec_contains(const char *spec, const char *wanted_token) {
9568+
char buf[CLI_BUF_1K];
9569+
snprintf(buf, sizeof(buf), "%s", spec ? spec : "");
9570+
char *save = NULL;
9571+
for (char *tok = strtok_r(buf, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
9572+
while (*tok == ' ') {
9573+
tok++;
9574+
}
9575+
size_t len = strlen(tok);
9576+
while (len > 0 && tok[len - 1] == ' ') {
9577+
tok[--len] = '\0';
9578+
}
9579+
if (strcmp(tok, wanted_token) == 0) {
9580+
return true;
9581+
}
9582+
}
9583+
return false;
9584+
}
9585+
9586+
static bool cli_clients_selects_registry_client(const char *spec, cbm_agent_client_id_t client_id) {
9587+
const cbm_agent_client_profile_t *profile = cbm_agent_client_by_id(client_id);
9588+
return profile && cli_clients_spec_contains(spec, profile->stable_id);
9589+
}
9590+
95569591
/* Restrict `detected` to the comma-separated token list. Returns false (after
95579592
* printing the vocabulary) when a token is unknown, so a typo can never be
95589593
* mistaken for "that client was not installed". */
@@ -9581,6 +9616,9 @@ static bool cli_clients_apply_selection(const char *spec, cbm_detected_agents_t
95819616
break;
95829617
}
95839618
}
9619+
if (!matched) {
9620+
matched = cbm_agent_client_by_stable_id(tok) != NULL;
9621+
}
95849622
if (!matched) {
95859623
(void)fprintf(stderr, "error: unknown client: %s\n\n", tok);
95869624
cli_clients_print_list(stderr);
@@ -9606,6 +9644,9 @@ size_t cbm_cli_clients_count_for_testing(void) {
96069644
const char *cbm_cli_clients_token_for_testing(size_t index) {
96079645
return index < CLI_CLIENT_COUNT ? CLI_CLIENTS[index].token : NULL;
96089646
}
9647+
void cbm_cli_set_client_selection_for_testing(const char *spec) {
9648+
g_client_selection = spec;
9649+
}
96099650
#endif
96109651

96119652
/* ── Subcommand: install ──────────────────────────────────────── */
@@ -10018,7 +10059,7 @@ int cbm_cmd_install(int argc, char **argv) {
1001810059
}
1001910060
} else if (strcmp(argv[i], "--clients") == 0) {
1002010061
/* Bare `--clients` (and `--clients=help`/`list`) print the
10021-
* vocabulary. 26 clients ship with tokens nobody would guess. */
10062+
* vocabulary, including tokens users would not readily guess. */
1002210063
cli_clients_print_list(stdout);
1002310064
return 0;
1002410065
} 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
@@ -208,6 +208,7 @@ int cbm_install_agent_configs(const char *home, const char *binary_path, bool fo
208208
bool cbm_cli_clients_apply_selection_for_testing(const char *spec, cbm_detected_agents_t *detected);
209209
size_t cbm_cli_clients_count_for_testing(void);
210210
const char *cbm_cli_clients_token_for_testing(size_t index);
211+
void cbm_cli_set_client_selection_for_testing(const char *spec);
211212
#endif
212213

213214
#ifdef CBM_CLI_ENABLE_TEST_API

‎tests/test_cli.c‎

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

12826+
/* Registry-backed clients share the public selector vocabulary. */
12827+
cbm_detected_agents_t registry = all;
12828+
ASSERT_TRUE(cbm_cli_clients_apply_selection_for_testing("qoder", &registry));
12829+
ASSERT_FALSE(registry.claude_code);
12830+
ASSERT_FALSE(registry.cursor);
12831+
1282612832
/* Every token in the table must resolve — a client added to detection but
1282712833
* forgotten here is invisible to the selector. */
1282812834
for (size_t i = 0; i < cbm_cli_clients_count_for_testing(); i++) {
@@ -12834,6 +12840,34 @@ TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558) {
1283412840
PASS();
1283512841
}
1283612842

12843+
TEST(cli_clients_selector_filters_registry_installs_issue1798) {
12844+
char *tmpdir = th_mktempdir("cbm_cli_clients_registry");
12845+
ASSERT_NOT_NULL(tmpdir);
12846+
12847+
char qoder_dir[512];
12848+
char settings_path[512];
12849+
ASSERT(snprintf(qoder_dir, sizeof(qoder_dir), "%s/.qoder", tmpdir) > 0);
12850+
ASSERT(snprintf(settings_path, sizeof(settings_path), "%s/settings.json", qoder_dir) > 0);
12851+
ASSERT_EQ(test_mkdirp(qoder_dir), 0);
12852+
12853+
cbm_cli_set_client_selection_for_testing("cursor");
12854+
int excluded_rc = cbm_install_agent_configs(tmpdir, "/opt/codebase-memory-mcp", false, false);
12855+
struct stat settings_state;
12856+
bool excluded = stat(settings_path, &settings_state) != 0;
12857+
12858+
cbm_cli_set_client_selection_for_testing("qoder");
12859+
int included_rc = cbm_install_agent_configs(tmpdir, "/opt/codebase-memory-mcp", false, false);
12860+
bool included = stat(settings_path, &settings_state) == 0;
12861+
cbm_cli_set_client_selection_for_testing(NULL);
12862+
12863+
test_rmdir_r(tmpdir);
12864+
ASSERT_EQ(excluded_rc, 0);
12865+
ASSERT_TRUE(excluded);
12866+
ASSERT_EQ(included_rc, 0);
12867+
ASSERT_TRUE(included);
12868+
PASS();
12869+
}
12870+
1283712871
TEST(cli_update_accepts_retired_variant_flags_issue1544) {
1283812872
char *ui_argv[] = {"--dry-run", "--ui", "--yes"};
1283912873
char *standard_argv[] = {"--dry-run", "--standard", "--yes"};
@@ -12981,6 +13015,7 @@ SUITE(cli) {
1298113015
RUN_TEST(cli_skill_frontmatter_scalars_with_colons_are_quoted_issue1554);
1298213016
RUN_TEST(cli_external_manager_detection_needs_positive_evidence_issue1566);
1298313017
RUN_TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558);
13018+
RUN_TEST(cli_clients_selector_filters_registry_installs_issue1798);
1298413019
RUN_TEST(cli_update_accepts_retired_variant_flags_issue1544);
1298513020
RUN_TEST(cli_update_agent_configs_finish_before_guard_release);
1298613021
RUN_TEST(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index);

0 commit comments

Comments
 (0)