@@ -3310,10 +3310,85 @@ static int select_update_variant(int variant_flag) {
33103310 return (choice [0 ] == '2' ) ? CLI_TRUE : 0 ;
33113311}
33123312
3313+ /* Case-insensitive prefix match (portable — no strncasecmp dependency). */
3314+ static bool prefix_icase (const char * s , const char * prefix ) {
3315+ while (* prefix ) {
3316+ if (tolower ((unsigned char )* s ) != tolower ((unsigned char )* prefix )) {
3317+ return false;
3318+ }
3319+ s ++ ;
3320+ prefix ++ ;
3321+ }
3322+ return true;
3323+ }
3324+
3325+ /* Fetch latest release tag from GitHub via redirect header.
3326+ * Returns heap-allocated tag (e.g. "v0.5.7") or NULL on failure. */
3327+ static char * fetch_latest_tag (void ) {
3328+ FILE * fp = cbm_popen (
3329+ "curl -sfI https://github.com/DeusData/codebase-memory-mcp/releases/latest 2>/dev/null" ,
3330+ "r" );
3331+ if (!fp ) {
3332+ return NULL ;
3333+ }
3334+ char line [CBM_SZ_512 ];
3335+ char * tag = NULL ;
3336+ while (fgets (line , sizeof (line ), fp )) {
3337+ if (!prefix_icase (line , "location:" )) {
3338+ continue ;
3339+ }
3340+ char * slash = strrchr (line , '/' );
3341+ if (!slash ) {
3342+ break ;
3343+ }
3344+ slash ++ ;
3345+ size_t len = strlen (slash );
3346+ while (len > 0 && (slash [len - SKIP_ONE ] == '\r' || slash [len - SKIP_ONE ] == '\n' ||
3347+ slash [len - SKIP_ONE ] == ' ' )) {
3348+ slash [-- len ] = '\0' ;
3349+ }
3350+ if (len > 0 ) {
3351+ tag = strdup (slash );
3352+ }
3353+ break ;
3354+ }
3355+ cbm_pclose (fp );
3356+ return tag ;
3357+ }
3358+
3359+ /* Check if current version is already latest. Returns true to skip update. */
3360+ static bool check_already_latest (void ) {
3361+ char dl_env [CBM_SZ_256 ] = "" ;
3362+ cbm_safe_getenv ("CBM_DOWNLOAD_URL" , dl_env , sizeof (dl_env ), NULL );
3363+ if (dl_env [0 ]) {
3364+ return false; /* testing override — always update */
3365+ }
3366+ char * latest = fetch_latest_tag ();
3367+ if (!latest ) {
3368+ (void )fprintf (stderr , "warning: could not check latest version (network unavailable?). "
3369+ "Proceeding with update.\n" );
3370+ return false;
3371+ }
3372+ int cmp = cbm_compare_versions (latest , CBM_VERSION );
3373+ if (cmp <= 0 ) {
3374+ if (cmp < 0 ) {
3375+ printf ("Already up to date (%s, ahead of latest %s).\n" , CBM_VERSION , latest );
3376+ } else {
3377+ printf ("Already up to date (%s).\n" , CBM_VERSION );
3378+ }
3379+ free (latest );
3380+ return true;
3381+ }
3382+ printf ("Update available: %s -> %s\n" , CBM_VERSION , latest );
3383+ free (latest );
3384+ return false;
3385+ }
3386+
33133387int cbm_cmd_update (int argc , char * * argv ) {
33143388 parse_auto_answer (argc , argv );
33153389
33163390 bool dry_run = false;
3391+ bool force = false;
33173392 int variant_flag = 0 ; /* 0 = ask, 1 = standard, 2 = ui */
33183393 for (int i = 0 ; i < argc ; i ++ ) {
33193394 if (strcmp (argv [i ], "--dry-run" ) == 0 ) {
@@ -3322,6 +3397,8 @@ int cbm_cmd_update(int argc, char **argv) {
33223397 variant_flag = VARIANT_A ;
33233398 } else if (strcmp (argv [i ], "--ui" ) == 0 ) {
33243399 variant_flag = VARIANT_B ;
3400+ } else if (strcmp (argv [i ], "--force" ) == 0 ) {
3401+ force = true;
33253402 }
33263403 }
33273404
@@ -3333,6 +3410,11 @@ int cbm_cmd_update(int argc, char **argv) {
33333410
33343411 printf ("codebase-memory-mcp update (current: %s)\n\n" , CBM_VERSION );
33353412
3413+ /* Version check — skip download if already on latest. */
3414+ if (!force && check_already_latest ()) {
3415+ return 0 ;
3416+ }
3417+
33363418 /* Step 1: Check for existing indexes */
33373419 if (update_clear_indexes (home , dry_run ) != 0 ) {
33383420 return CLI_TRUE ;
0 commit comments