@@ -1349,6 +1349,208 @@ def version():
13491349 console .print (panel )
13501350 console .print ()
13511351
1352+ def _get_config_path (project_path : Path = None ) -> Path :
1353+ """Get the path to .specify/config.json file."""
1354+ if project_path is None :
1355+ project_path = Path .cwd ()
1356+ return project_path / ".specify" / "config.json"
1357+
1358+ def _load_config (project_path : Path = None ) -> dict :
1359+ """Load configuration from .specify/config.json, return default if not exists."""
1360+ config_path = _get_config_path (project_path )
1361+ default_config = {
1362+ "progress" : {
1363+ "autoTracking" : False ,
1364+ "updateOnTaskComplete" : True ,
1365+ "updateOnPhaseComplete" : True
1366+ },
1367+ "version" : "1.0"
1368+ }
1369+
1370+ if not config_path .exists ():
1371+ return default_config
1372+
1373+ try :
1374+ with open (config_path , 'r' , encoding = 'utf-8' ) as f :
1375+ user_config = json .load (f )
1376+ # Merge with defaults to ensure all keys exist
1377+ merged = default_config .copy ()
1378+ if "progress" in user_config :
1379+ merged ["progress" ].update (user_config ["progress" ])
1380+ if "version" in user_config :
1381+ merged ["version" ] = user_config ["version" ]
1382+ return merged
1383+ except (json .JSONDecodeError , IOError ) as e :
1384+ console .print (f"[yellow]Warning:[/yellow] Could not read config file: { e } " )
1385+ console .print ("[dim]Using default configuration[/dim]" )
1386+ return default_config
1387+
1388+ def _save_config (config : dict , project_path : Path = None ) -> bool :
1389+ """Save configuration to .specify/config.json."""
1390+ config_path = _get_config_path (project_path )
1391+ config_dir = config_path .parent
1392+
1393+ try :
1394+ # Ensure .specify directory exists
1395+ config_dir .mkdir (parents = True , exist_ok = True )
1396+
1397+ with open (config_path , 'w' , encoding = 'utf-8' ) as f :
1398+ json .dump (config , f , indent = 2 )
1399+ f .write ('\n ' )
1400+ return True
1401+ except IOError as e :
1402+ console .print (f"[red]Error:[/red] Could not write config file: { e } " )
1403+ return False
1404+
1405+ @app .command ()
1406+ def config (
1407+ show : bool = typer .Option (False , "--show" , "-s" , help = "Show current configuration" ),
1408+ get : str = typer .Option (None , "--get" , "-g" , help = "Get a specific configuration value (e.g., 'progress.autoTracking')" ),
1409+ set_key : str = typer .Option (None , "--set" , help = "Set a configuration key (e.g., 'progress.autoTracking')" ),
1410+ set_value : str = typer .Option (None , help = "Value for --set (true/false for booleans)" ),
1411+ auto_tracking : bool = typer .Option (False , "--auto-tracking/--no-auto-tracking" , help = "Enable or disable auto-tracking" ),
1412+ project_path : str = typer .Option (None , "--project" , "-p" , help = "Project path (default: current directory)" ),
1413+ ):
1414+ """
1415+ Manage Specify project configuration.
1416+
1417+ View or modify settings for progress tracking and other features.
1418+
1419+ Examples:
1420+ specify config --show # Show current configuration
1421+ specify config --get progress.autoTracking # Get specific setting
1422+ specify config --auto-tracking true # Enable auto-tracking
1423+ specify config --set progress.autoTracking false # Set specific setting
1424+ """
1425+ show_banner ()
1426+
1427+ # Determine project path
1428+ if project_path :
1429+ proj_path = Path (project_path ).resolve ()
1430+ if not proj_path .exists ():
1431+ console .print (f"[red]Error:[/red] Project path does not exist: { project_path } " )
1432+ raise typer .Exit (1 )
1433+ else :
1434+ proj_path = Path .cwd ()
1435+
1436+ # Check if this is a Specify project
1437+ specify_dir = proj_path / ".specify"
1438+ if not specify_dir .exists ():
1439+ console .print ("[yellow]Warning:[/yellow] This doesn't appear to be a Specify project." )
1440+ console .print ("[dim]Configuration will be created in current directory[/dim]\n " )
1441+
1442+ # Load current config
1443+ current_config = _load_config (proj_path )
1444+
1445+ # Handle different operations
1446+ # Check if --auto-tracking or --no-auto-tracking was explicitly used
1447+ # Typer sets the flag to True if --auto-tracking, False if --no-auto-tracking
1448+ # We need to detect if user actually used the flag (not just default False)
1449+ import sys
1450+ auto_tracking_used = "--auto-tracking" in sys .argv or "--no-auto-tracking" in sys .argv
1451+
1452+ if auto_tracking_used :
1453+ # Quick set for auto-tracking
1454+ current_config ["progress" ]["autoTracking" ] = auto_tracking
1455+ if _save_config (current_config , proj_path ):
1456+ status = "enabled" if auto_tracking else "disabled"
1457+ console .print (f"[green]✓[/green] Auto-tracking { status } " )
1458+ console .print (f"[dim]Configuration saved to: { _get_config_path (proj_path )} [/dim]" )
1459+ else :
1460+ raise typer .Exit (1 )
1461+ return
1462+
1463+ if set_key and set_value is not None :
1464+ # Set specific key
1465+ keys = set_key .split ('.' )
1466+ config_ref = current_config
1467+ for key in keys [:- 1 ]:
1468+ if key not in config_ref :
1469+ config_ref [key ] = {}
1470+ config_ref = config_ref [key ]
1471+
1472+ # Convert value based on type
1473+ final_key = keys [- 1 ]
1474+ if isinstance (config_ref .get (final_key ), bool ):
1475+ # Boolean conversion
1476+ if set_value .lower () in ('true' , '1' , 'yes' , 'on' ):
1477+ config_ref [final_key ] = True
1478+ elif set_value .lower () in ('false' , '0' , 'no' , 'off' ):
1479+ config_ref [final_key ] = False
1480+ else :
1481+ console .print (f"[red]Error:[/red] Invalid boolean value: { set_value } " )
1482+ console .print ("[dim]Use: true, false, 1, 0, yes, no, on, off[/dim]" )
1483+ raise typer .Exit (1 )
1484+ elif isinstance (config_ref .get (final_key ), int ):
1485+ try :
1486+ config_ref [final_key ] = int (set_value )
1487+ except ValueError :
1488+ console .print (f"[red]Error:[/red] Invalid integer value: { set_value } " )
1489+ raise typer .Exit (1 )
1490+ else :
1491+ config_ref [final_key ] = set_value
1492+
1493+ if _save_config (current_config , proj_path ):
1494+ console .print (f"[green]✓[/green] Set { set_key } = { config_ref [final_key ]} " )
1495+ console .print (f"[dim]Configuration saved to: { _get_config_path (proj_path )} [/dim]" )
1496+ else :
1497+ raise typer .Exit (1 )
1498+ return
1499+
1500+ if get :
1501+ # Get specific key
1502+ keys = get .split ('.' )
1503+ value = current_config
1504+ try :
1505+ for key in keys :
1506+ value = value [key ]
1507+ console .print (f"[cyan]{ get } :[/cyan] { value } " )
1508+ except KeyError :
1509+ console .print (f"[red]Error:[/red] Configuration key not found: { get } " )
1510+ raise typer .Exit (1 )
1511+ return
1512+
1513+ # Default: show all configuration
1514+ config_table = Table (show_header = False , box = None , padding = (0 , 2 ))
1515+ config_table .add_column ("Setting" , style = "cyan" , justify = "left" )
1516+ config_table .add_column ("Value" , style = "white" , justify = "left" )
1517+
1518+ # Progress settings
1519+ config_table .add_row ("" , "" )
1520+ config_table .add_row ("[bold]Progress Tracking[/bold]" , "" )
1521+ config_table .add_row (" autoTracking" , str (current_config ["progress" ]["autoTracking" ]))
1522+ config_table .add_row (" updateOnTaskComplete" , str (current_config ["progress" ]["updateOnTaskComplete" ]))
1523+ config_table .add_row (" updateOnPhaseComplete" , str (current_config ["progress" ]["updateOnPhaseComplete" ]))
1524+
1525+ # Version
1526+ config_table .add_row ("" , "" )
1527+ config_table .add_row ("[bold]Version[/bold]" , current_config .get ("version" , "1.0" ))
1528+
1529+ config_panel = Panel (
1530+ config_table ,
1531+ title = "[bold cyan]Specify Configuration[/bold cyan]" ,
1532+ border_style = "cyan" ,
1533+ padding = (1 , 2 )
1534+ )
1535+
1536+ console .print (config_panel )
1537+ console .print ()
1538+
1539+ # Show file location
1540+ config_file = _get_config_path (proj_path )
1541+ if config_file .exists ():
1542+ console .print (f"[dim]Configuration file: { config_file } [/dim]" )
1543+ else :
1544+ console .print (f"[dim]Configuration file: { config_file } (using defaults)[/dim]" )
1545+
1546+ console .print ()
1547+ console .print ("[bold]Usage examples:[/bold]" )
1548+ console .print (" [cyan]specify config --auto-tracking[/cyan] # Enable auto-tracking" )
1549+ console .print (" [cyan]specify config --no-auto-tracking[/cyan] # Disable auto-tracking" )
1550+ console .print (" [cyan]specify config --get progress.autoTracking[/cyan] # Get specific setting" )
1551+ console .print (" [cyan]specify config --set progress.autoTracking true[/cyan] # Set specific setting" )
1552+ console .print ()
1553+
13521554def main ():
13531555 app ()
13541556
0 commit comments