| 5968 | /* ── PATH management ──────────────────────────────────────────── */ |
| 5969 | |
| 5970 | int cbm_ensure_path(const char *bin_dir, const char *rc_file, bool dry_run) { |
| 5971 | if (!bin_dir || !rc_file) { |
| 5972 | return CLI_ERR; |
| 5973 | } |
| 5974 | |
| 5975 | /* fish uses a different syntax than POSIX shells: `export PATH="...:$PATH"` |
| 5976 | * is a syntax error in fish and breaks config.fish (#319). When the target |
| 5977 | * is a fish config, emit the fish-native `fish_add_path` (idempotent, |
| 5978 | * prepends only if absent) instead. */ |
| 5979 | size_t rc_len = strlen(rc_file); |
| 5980 | bool is_fish = rc_len >= CBM_SZ_5 && strcmp(rc_file + rc_len - CBM_SZ_5, ".fish") == 0; |
| 5981 | |
| 5982 | char line[CLI_BUF_1K]; |
| 5983 | if (is_fish) { |
| 5984 | snprintf(line, sizeof(line), "fish_add_path %s", bin_dir); |
| 5985 | } else { |
| 5986 | snprintf(line, sizeof(line), "export PATH=\"%s:$PATH\"", bin_dir); |
| 5987 | } |
| 5988 | |
| 5989 | /* Check if already present in rc file */ |
| 5990 | FILE *f = fopen(rc_file, "r"); |
| 5991 | if (f) { |
| 5992 | char buf[CLI_BUF_2K]; |
| 5993 | while (fgets(buf, sizeof(buf), f)) { |
| 5994 | if (strstr(buf, line)) { |
| 5995 | (void)fclose(f); |
| 5996 | return CLI_TRUE; /* already present */ |
| 5997 | } |
| 5998 | } |
| 5999 | (void)fclose(f); |
| 6000 | } |
| 6001 | |
| 6002 | if (dry_run) { |
| 6003 | return 0; |
| 6004 | } |
| 6005 | |
| 6006 | f = fopen(rc_file, "a"); |
| 6007 | if (!f) { |
| 6008 | return CLI_ERR; |
| 6009 | } |
| 6010 | |
| 6011 | (void)fprintf(f, "\n# Added by codebase-memory-mcp install\n%s\n", line); |
| 6012 | (void)fclose(f); |
| 6013 | return 0; |
| 6014 | } |
| 6015 | |
| 6016 | #ifdef _WIN32 |
| 6017 | static wchar_t *cli_windows_utf8_to_wide(const char *value) { |
no outgoing calls
no test coverage detected