| 6503 | /* ── PATH management ──────────────────────────────────────────── */ |
| 6504 | |
| 6505 | int cbm_ensure_path(const char *bin_dir, const char *rc_file, bool dry_run) { |
| 6506 | if (!bin_dir || !rc_file) { |
| 6507 | return CLI_ERR; |
| 6508 | } |
| 6509 | |
| 6510 | /* fish uses a different syntax than POSIX shells: `export PATH="...:$PATH"` |
| 6511 | * is a syntax error in fish and breaks config.fish (#319). When the target |
| 6512 | * is a fish config, emit the fish-native `fish_add_path` (idempotent, |
| 6513 | * prepends only if absent) instead. */ |
| 6514 | size_t rc_len = strlen(rc_file); |
| 6515 | bool is_fish = rc_len >= CBM_SZ_5 && strcmp(rc_file + rc_len - CBM_SZ_5, ".fish") == 0; |
| 6516 | |
| 6517 | char line[CLI_BUF_1K]; |
| 6518 | if (is_fish) { |
| 6519 | snprintf(line, sizeof(line), "fish_add_path %s", bin_dir); |
| 6520 | } else { |
| 6521 | snprintf(line, sizeof(line), "export PATH=\"%s:$PATH\"", bin_dir); |
| 6522 | } |
| 6523 | |
| 6524 | /* Check if already present in rc file */ |
| 6525 | FILE *f = fopen(rc_file, "r"); |
| 6526 | if (f) { |
| 6527 | char buf[CLI_BUF_2K]; |
| 6528 | while (fgets(buf, sizeof(buf), f)) { |
| 6529 | if (strstr(buf, line)) { |
| 6530 | (void)fclose(f); |
| 6531 | return CLI_TRUE; /* already present */ |
| 6532 | } |
| 6533 | } |
| 6534 | (void)fclose(f); |
| 6535 | } |
| 6536 | |
| 6537 | if (dry_run) { |
| 6538 | return 0; |
| 6539 | } |
| 6540 | |
| 6541 | f = fopen(rc_file, "a"); |
| 6542 | if (!f) { |
| 6543 | return CLI_ERR; |
| 6544 | } |
| 6545 | |
| 6546 | (void)fprintf(f, "\n# Added by codebase-memory-mcp install\n%s\n", line); |
| 6547 | (void)fclose(f); |
| 6548 | return 0; |
| 6549 | } |
| 6550 | |
| 6551 | #ifdef _WIN32 |
| 6552 | static wchar_t *cli_windows_utf8_to_wide(const char *value) { |
no outgoing calls
no test coverage detected