Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 142 additions & 17 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -1953,18 +1953,54 @@ static const char *detect_arch(void) {
#endif
}

/* ── Claude config dir helper ─────────────────────────────────── */

/* Return the Claude Code config directory.
* Checks CLAUDE_CONFIG_DIR env var first; falls back to {home}/.claude.
* Result is written into buf (size n). Returns buf, or NULL on error. */
char *cbm_claude_config_dir(const char *home, char *buf, size_t n) {
const char *env = getenv("CLAUDE_CONFIG_DIR");
if (env && env[0]) {
snprintf(buf, n, "%s", env);
} else {
snprintf(buf, n, "%s/.claude", home);
}
return buf;
}

/* Internal alias used within this file */
static char *claude_config_dir(const char *home, char *buf, size_t n) {
return cbm_claude_config_dir(home, buf, n);
}

/* ── Subcommand: install ──────────────────────────────────────── */

int cbm_cmd_install(int argc, char **argv) {
parse_auto_answer(argc, argv);
bool dry_run = false;
bool force = false;
bool has_project = false;
char project_path[1024];
project_path[0] = '\0';

for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--dry-run") == 0) {
dry_run = true;
}
if (strcmp(argv[i], "--force") == 0) {
} else if (strcmp(argv[i], "--force") == 0) {
force = true;
} else if (strcmp(argv[i], "--project") == 0) {
has_project = true;
/* Consume the next arg as the project path if it doesn't look like a flag */
if (i + 1 < argc && argv[i + 1][0] != '-') {
snprintf(project_path, sizeof(project_path), "%s", argv[i + 1]);
i++;
} else {
/* No path provided — use cwd */
if (!getcwd(project_path, sizeof(project_path))) {
fprintf(stderr, "error: getcwd failed\n");
return 1;
}
}
}
}

Expand All @@ -1974,6 +2010,16 @@ int cbm_cmd_install(int argc, char **argv) {
return 1;
}

/* Determine the Claude Code config base directory:
* --project overrides to {project_path}/.claude;
* otherwise CLAUDE_CONFIG_DIR env var overrides ~/.claude. */
char claude_dir[1024];
if (has_project) {
snprintf(claude_dir, sizeof(claude_dir), "%s/.claude", project_path);
} else {
claude_config_dir(home, claude_dir, sizeof(claude_dir));
}

printf("codebase-memory-mcp install %s\n\n", CBM_VERSION);

/* Step 1: Check for existing indexes */
Expand Down Expand Up @@ -2011,8 +2057,14 @@ int cbm_cmd_install(int argc, char **argv) {
char self_path[1024];
snprintf(self_path, sizeof(self_path), "%s/.local/bin/codebase-memory-mcp", home);

/* Step 3: Detect agents */
/* Step 3: Detect agents.
* When --project is set, we force Claude Code to true (we always install
* into the project's .claude/ dir) and still detect all other agents from
* the global home directory. */
cbm_detected_agents_t agents = cbm_detect_agents(home);
if (has_project) {
agents.claude_code = true;
}
printf("Detected agents:");
if (agents.claude_code) {
printf(" Claude-Code");
Expand Down Expand Up @@ -2047,7 +2099,7 @@ int cbm_cmd_install(int argc, char **argv) {
/* Step 4: Install Claude Code skills + hooks */
if (agents.claude_code) {
char skills_dir[1024];
snprintf(skills_dir, sizeof(skills_dir), "%s/.claude/skills", home);
snprintf(skills_dir, sizeof(skills_dir), "%s/skills", claude_dir);
printf("Claude Code:\n");

int skill_count = cbm_install_skills(skills_dir, force, dry_run);
Expand All @@ -2059,21 +2111,27 @@ int cbm_cmd_install(int argc, char **argv) {

/* MCP config (.mcp.json) */
char mcp_path[1024];
snprintf(mcp_path, sizeof(mcp_path), "%s/.claude/.mcp.json", home);
snprintf(mcp_path, sizeof(mcp_path), "%s/.mcp.json", claude_dir);
if (!dry_run) {
cbm_install_editor_mcp(self_path, mcp_path);
}
printf(" mcp: %s\n", mcp_path);

/* PreToolUse hook */
char settings_path[1024];
snprintf(settings_path, sizeof(settings_path), "%s/.claude/settings.json", home);
snprintf(settings_path, sizeof(settings_path), "%s/settings.json", claude_dir);
if (!dry_run) {
cbm_upsert_claude_hooks(settings_path);
}
printf(" hooks: PreToolUse (Grep|Glob reminder)\n");
}

/* Steps 5-12: Global agent installs — skipped when --project is used
* because those tools use their own global config directories, not the
* project-local .claude/ tree. */
const char *shell_rc = "";
if (!has_project) {

/* Step 5: Install Codex CLI */
if (agents.codex) {
printf("Codex CLI:\n");
Expand Down Expand Up @@ -2203,18 +2261,23 @@ int cbm_cmd_install(int argc, char **argv) {
/* Step 12: Ensure PATH */
char bin_dir[1024];
snprintf(bin_dir, sizeof(bin_dir), "%s/.local/bin", home);
const char *rc = cbm_detect_shell_rc(home);
if (rc[0]) {
int path_rc = cbm_ensure_path(bin_dir, rc, dry_run);
shell_rc = cbm_detect_shell_rc(home);
if (shell_rc[0]) {
int path_rc = cbm_ensure_path(bin_dir, shell_rc, dry_run);
if (path_rc == 0) {
printf("\nAdded %s to PATH in %s\n", bin_dir, rc);
printf("\nAdded %s to PATH in %s\n", bin_dir, shell_rc);
} else if (path_rc == 1) {
printf("\nPATH already includes %s\n", bin_dir);
}
}

printf("\nInstall complete. Restart your shell or run:\n");
printf(" source %s\n", rc);
} /* end if (!has_project) */

printf("\nInstall complete.");
if (!has_project && shell_rc[0]) {
printf(" Restart your shell or run:\n source %s", shell_rc);
}
printf("\n");
if (dry_run) {
printf("\n(dry-run — no files were modified)\n");
}
Expand All @@ -2226,9 +2289,24 @@ int cbm_cmd_install(int argc, char **argv) {
int cbm_cmd_uninstall(int argc, char **argv) {
parse_auto_answer(argc, argv);
bool dry_run = false;
bool has_project = false;
char project_path[1024];
project_path[0] = '\0';

for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--dry-run") == 0) {
dry_run = true;
} else if (strcmp(argv[i], "--project") == 0) {
has_project = true;
if (i + 1 < argc && argv[i + 1][0] != '-') {
snprintf(project_path, sizeof(project_path), "%s", argv[i + 1]);
i++;
} else {
if (!getcwd(project_path, sizeof(project_path))) {
fprintf(stderr, "error: getcwd failed\n");
return 1;
}
}
}
}

Expand All @@ -2238,32 +2316,47 @@ int cbm_cmd_uninstall(int argc, char **argv) {
return 1;
}

/* Determine the Claude Code config base directory */
char claude_dir[1024];
if (has_project) {
snprintf(claude_dir, sizeof(claude_dir), "%s/.claude", project_path);
} else {
claude_config_dir(home, claude_dir, sizeof(claude_dir));
}

printf("codebase-memory-mcp uninstall\n\n");

/* Step 1: Detect agents and remove per-agent configs */
cbm_detected_agents_t agents = cbm_detect_agents(home);
if (has_project) {
agents.claude_code = true;
}

if (agents.claude_code) {
char skills_dir[1024];
snprintf(skills_dir, sizeof(skills_dir), "%s/.claude/skills", home);
snprintf(skills_dir, sizeof(skills_dir), "%s/skills", claude_dir);
int removed = cbm_remove_skills(skills_dir, dry_run);
printf("Claude Code: removed %d skill(s)\n", removed);

char mcp_path[1024];
snprintf(mcp_path, sizeof(mcp_path), "%s/.claude/.mcp.json", home);
snprintf(mcp_path, sizeof(mcp_path), "%s/.mcp.json", claude_dir);
if (!dry_run) {
cbm_remove_editor_mcp(mcp_path);
}
printf(" removed MCP config entry\n");

char settings_path[1024];
snprintf(settings_path, sizeof(settings_path), "%s/.claude/settings.json", home);
snprintf(settings_path, sizeof(settings_path), "%s/settings.json", claude_dir);
if (!dry_run) {
cbm_remove_claude_hooks(settings_path);
}
printf(" removed PreToolUse hook\n");
}

/* Steps below (other agents, indexes, binary) are global-only — skip when
* --project is used so only the project-local .claude/ tree is touched. */
if (!has_project) {

if (agents.codex) {
char config_path[1024];
snprintf(config_path, sizeof(config_path), "%s/.codex/config.toml", home);
Expand Down Expand Up @@ -2413,6 +2506,8 @@ int cbm_cmd_uninstall(int argc, char **argv) {
printf("Removed %s\n", bin_path);
}

} /* end if (!has_project) */

printf("\nUninstall complete.\n");
if (dry_run) {
printf("(dry-run — no files were modified)\n");
Expand All @@ -2425,6 +2520,25 @@ int cbm_cmd_uninstall(int argc, char **argv) {
int cbm_cmd_update(int argc, char **argv) {
parse_auto_answer(argc, argv);

bool has_project = false;
char project_path[1024];
project_path[0] = '\0';

for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--project") == 0) {
has_project = true;
if (i + 1 < argc && argv[i + 1][0] != '-') {
snprintf(project_path, sizeof(project_path), "%s", argv[i + 1]);
i++;
} else {
if (!getcwd(project_path, sizeof(project_path))) {
fprintf(stderr, "error: getcwd failed\n");
return 1;
}
}
}
}

const char *home = getenv("HOME");
if (!home) {
fprintf(stderr, "error: HOME not set\n");
Expand Down Expand Up @@ -2590,10 +2704,21 @@ int cbm_cmd_update(int argc, char **argv) {
}

/* Step 6: Reinstall skills (force to pick up new content) */
char claude_dir_upd[1024];
claude_config_dir(home, claude_dir_upd, sizeof(claude_dir_upd));
char skills_dir[1024];
snprintf(skills_dir, sizeof(skills_dir), "%s/.claude/skills", home);
snprintf(skills_dir, sizeof(skills_dir), "%s/skills", claude_dir_upd);
int skill_count = cbm_install_skills(skills_dir, true, false);
printf("Updated %d skill(s).\n", skill_count);
printf("Updated %d global skill(s).\n", skill_count);

/* Also update project-local skills if --project was specified. */
if (has_project) {
char proj_skills_dir[1024];
snprintf(proj_skills_dir, sizeof(proj_skills_dir), "%s/.claude/skills", project_path);
int proj_skill_count = cbm_install_skills(proj_skills_dir, true, false);
printf("Updated %d project skill(s) in %s/.claude/skills.\n", proj_skill_count,
project_path);
}

/* Step 7: Verify new version */
printf("\nUpdate complete. Verifying:\n");
Expand Down
8 changes: 8 additions & 0 deletions src/cli/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define CBM_CLI_H

#include <stdbool.h>
#include <stddef.h>

/* ── Version ──────────────────────────────────────────────────── */

Expand Down Expand Up @@ -231,6 +232,13 @@ int cbm_config_delete(cbm_config_t *cfg, const char *key);
#define CBM_CONFIG_AUTO_INDEX "auto_index"
#define CBM_CONFIG_AUTO_INDEX_LIMIT "auto_index_limit"

/* ── Claude config dir helper ─────────────────────────────────── */

/* Return the Claude Code config directory into buf (size n).
* Checks CLAUDE_CONFIG_DIR env var first; falls back to {home}/.claude.
* Returns buf on success, NULL on error. */
char *cbm_claude_config_dir(const char *home, char *buf, size_t n);

/* ── Subcommands (wired from main.c) ─────────────────────────── */

/* install: copy binary, install skills, install editor MCP configs, ensure PATH.
Expand Down
Loading