diff --git a/src/cli/cli.c b/src/cli/cli.c index f159f591..79a5e003 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -1143,6 +1143,10 @@ cbm_detected_agents_t cbm_detect_agents(const char *home_dir) { snprintf(path, sizeof(path), "%s/.kiro", home_dir); agents.kiro = dir_exists(path); + /* Junie (JetBrains): ~/.junie/ */ + snprintf(path, sizeof(path), "%s/.junie", home_dir); + agents.junie = dir_exists(path); + return agents; } @@ -1679,6 +1683,17 @@ int cbm_remove_antigravity_mcp(const char *config_path) { return cbm_remove_editor_mcp(config_path); } +/* ── Junie MCP config (JSON, same mcpServers format) ──────────── */ + +int cbm_upsert_junie_mcp(const char *binary_path, const char *config_path) { + /* Junie (JetBrains) uses same mcpServers format as Cursor/Antigravity */ + return cbm_install_editor_mcp(binary_path, config_path); +} + +int cbm_remove_junie_mcp(const char *config_path) { + return cbm_remove_editor_mcp(config_path); +} + /* ── Claude Code pre-tool hooks ───────────────────────────────── */ /* Matcher intentionally excludes Read: gating Read breaks Claude Code's @@ -2934,6 +2949,7 @@ static void print_detected_agents(const cbm_detected_agents_t *a) { {a->cursor, "Cursor"}, {a->openclaw, "OpenClaw"}, {a->kiro, "Kiro"}, + {a->junie, "Junie"}, }; printf("Detected agents:"); bool any = false; @@ -3247,6 +3263,16 @@ static void install_editor_agent_configs(const cbm_detected_agents_t *agents, co install_generic_agent_config("Kiro", binary_path, cp, NULL, dry_run, cbm_install_editor_mcp); } + if (agents->junie) { + char cp[CLI_BUF_1K]; + char sd[CLI_BUF_1K]; + snprintf(cp, sizeof(cp), "%s/.junie/mcp/mcp.json", home); + snprintf(sd, sizeof(sd), "%s/.junie/mcp", home); + if (!dry_run) { + cbm_mkdir_p(sd, CLI_OCTAL_PERM); + } + install_generic_agent_config("Junie", binary_path, cp, NULL, dry_run, cbm_upsert_junie_mcp); + } } static void cbm_install_agent_configs(const char *home, const char *binary_path, bool force, @@ -3346,6 +3372,7 @@ char *cbm_build_install_plan_json(const char *home, const char *binary_path) { {det.cursor, "cursor"}, {det.openclaw, "openclaw"}, {det.kiro, "kiro"}, + {det.junie, "junie"}, }; yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); @@ -3720,6 +3747,12 @@ static void uninstall_editor_agents(const cbm_detected_agents_t *agents, const c uninstall_agent_mcp_instr((mcp_uninstall_args_t){"Kiro", cp, NULL}, dry_run, cbm_remove_editor_mcp); } + if (agents->junie) { + char cp[CLI_BUF_1K]; + snprintf(cp, sizeof(cp), "%s/.junie/mcp/mcp.json", home); + uninstall_agent_mcp_instr((mcp_uninstall_args_t){"Junie", cp, NULL}, dry_run, + cbm_remove_junie_mcp); + } } int cbm_cmd_uninstall(int argc, char **argv) { diff --git a/src/cli/cli.h b/src/cli/cli.h index 9efe6789..52398d92 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -128,6 +128,7 @@ typedef struct { bool cursor; /* ~/.cursor/ exists */ bool openclaw; /* ~/.openclaw/ exists */ bool kiro; /* ~/.kiro/ exists */ + bool junie; /* ~/.junie/ exists */ } cbm_detected_agents_t; /* Detect which coding agents are installed. @@ -155,6 +156,13 @@ int cbm_upsert_antigravity_mcp(const char *binary_path, const char *config_path) /* Remove CMM MCP entry from antigravity mcp_config.json. Returns 0 on success. */ int cbm_remove_antigravity_mcp(const char *config_path); +/* Junie (JetBrains): upsert MCP entry in ~/.junie/mcp/mcp.json (mcpServers format). + * Returns 0 on success. */ +int cbm_upsert_junie_mcp(const char *binary_path, const char *config_path); + +/* Remove CMM MCP entry from Junie mcp.json. Returns 0 on success. */ +int cbm_remove_junie_mcp(const char *config_path); + /* ── Instructions file upsert ─────────────────────────────────── */ /* Upsert a codebase-memory-mcp instruction section in a markdown file. diff --git a/tests/test_cli.c b/tests/test_cli.c index 0b78537c..99a50d07 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -695,6 +695,48 @@ TEST(cli_editor_mcp_uninstall) { PASS(); } +TEST(cli_junie_mcp_install_issue651) { + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + FAIL("cbm_mkdtemp failed"); + + char configpath[512]; + snprintf(configpath, sizeof(configpath), "%s/.junie/mcp/mcp.json", tmpdir); + + int rc = cbm_upsert_junie_mcp("/usr/local/bin/codebase-memory-mcp", configpath); + ASSERT_EQ(rc, 0); + + const char *data = read_test_file(configpath); + ASSERT_NOT_NULL(data); + ASSERT(strstr(data, "mcpServers") != NULL); + ASSERT(strstr(data, "codebase-memory-mcp") != NULL); + ASSERT(strstr(data, "/usr/local/bin/codebase-memory-mcp") != NULL); + + rc = cbm_upsert_junie_mcp("/usr/local/bin/codebase-memory-mcp", configpath); + ASSERT_EQ(rc, 0); + + data = read_test_file(configpath); + ASSERT_NOT_NULL(data); + int count = 0; + const char *p = data; + while ((p = strstr(p, "\"codebase-memory-mcp\"")) != NULL) { + count++; + p += 20; + } + ASSERT_EQ(count, 1); + + rc = cbm_remove_junie_mcp(configpath); + ASSERT_EQ(rc, 0); + + data = read_test_file(configpath); + ASSERT_NOT_NULL(data); + ASSERT(strstr(data, "\"codebase-memory-mcp\"") == NULL); + + test_rmdir_r(tmpdir); + PASS(); +} + TEST(cli_gemini_mcp_install) { /* Port of TestGeminiMCPInstall */ char tmpdir[256]; @@ -1774,6 +1816,22 @@ TEST(cli_detect_agents_finds_kiro) { PASS(); } +/* issue #651: Junie (~/.junie/) must be detected so install registers the + * MCP server in ~/.junie/mcp/mcp.json. */ +TEST(cli_detect_agents_finds_junie_issue651) { + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + FAIL("cbm_mkdtemp failed"); + char dir[512]; + snprintf(dir, sizeof(dir), "%s/.junie", tmpdir); + test_mkdirp(dir); + cbm_detected_agents_t agents = cbm_detect_agents(tmpdir); + ASSERT_TRUE(agents.junie); + test_rmdir_r(tmpdir); + PASS(); +} + TEST(cli_detect_agents_none_found) { char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); @@ -2692,6 +2750,7 @@ SUITE(cli) { RUN_TEST(cli_editor_mcp_idempotent); RUN_TEST(cli_editor_mcp_preserves_others); RUN_TEST(cli_editor_mcp_uninstall); + RUN_TEST(cli_junie_mcp_install_issue651); RUN_TEST(cli_gemini_mcp_install); /* VS Code MCP (2 tests — install_test.go) */ @@ -2756,6 +2815,7 @@ SUITE(cli) { RUN_TEST(cli_detect_agents_finds_antigravity); RUN_TEST(cli_detect_agents_finds_kilocode); RUN_TEST(cli_detect_agents_finds_kiro); + RUN_TEST(cli_detect_agents_finds_junie_issue651); RUN_TEST(cli_detect_agents_none_found); /* Codex MCP config upsert (3 tests — group B) */