Skip to content

Commit 897b893

Browse files
committed
feat: add status-line configuration support
Add statusLine configuration support to environment YAML config: - status-line.file: script path to download to user config directory - status-line.padding: optional padding value for the status line Python scripts are executed via uv run, other files are executed directly.
1 parent a3c6b53 commit 897b893

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

scripts/setup_environment.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3250,6 +3250,7 @@ def create_additional_settings(
32503250
always_thinking_enabled: bool | None = None,
32513251
company_announcements: list[str] | None = None,
32523252
attribution: dict[str, str] | None = None,
3253+
status_line: dict[str, Any] | None = None,
32533254
) -> bool:
32543255
"""Create {command_name}-additional-settings.json with environment-specific settings.
32553256
@@ -3269,6 +3270,8 @@ def create_additional_settings(
32693270
company_announcements: Optional list of company announcement strings
32703271
attribution: Optional dict with 'commit' and 'pr' keys for custom attribution strings.
32713272
Empty strings hide attribution. Takes precedence over include_co_authored_by.
3273+
status_line: Optional dict with 'file' key for status line script path and optional
3274+
'padding' key. The file is downloaded to ~/.claude/hooks/ and configured in settings.
32723275
32733276
Returns:
32743277
bool: True if successful, False otherwise.
@@ -3331,6 +3334,38 @@ def create_additional_settings(
33313334
settings['companyAnnouncements'] = company_announcements
33323335
info(f'Setting companyAnnouncements: {len(company_announcements)} announcement(s)')
33333336

3337+
# Add statusLine if explicitly set (None means not configured, leave as default)
3338+
if status_line is not None:
3339+
status_line_file = status_line.get('file')
3340+
if status_line_file:
3341+
# Build absolute path to the hook file in .claude/hooks/
3342+
# Strip query parameters from filename
3343+
clean_filename = status_line_file.split('?')[0] if '?' in status_line_file else status_line_file
3344+
filename = Path(clean_filename).name
3345+
hook_path = claude_user_dir / 'hooks' / filename
3346+
hook_path_str = hook_path.as_posix()
3347+
3348+
# Determine command based on file extension
3349+
if filename.lower().endswith(('.py', '.pyw')):
3350+
# Python script - use uv run
3351+
status_line_command = f'uv run --no-project --python 3.12 {hook_path_str}'
3352+
else:
3353+
# Other file - use path directly
3354+
status_line_command = hook_path_str
3355+
3356+
status_line_config: dict[str, Any] = {
3357+
'type': 'command',
3358+
'command': status_line_command,
3359+
}
3360+
3361+
# Add optional padding
3362+
padding = status_line.get('padding')
3363+
if padding is not None:
3364+
status_line_config['padding'] = padding
3365+
3366+
settings['statusLine'] = status_line_config
3367+
info(f'Setting statusLine: {filename}')
3368+
33343369
# Handle hooks if present
33353370
hook_events: list[dict[str, Any]] = []
33363371

@@ -4147,6 +4182,9 @@ def main() -> None:
41474182
# Extract attribution configuration (new format, takes precedence over include-co-authored-by)
41484183
attribution = config.get('attribution')
41494184

4185+
# Extract status_line configuration
4186+
status_line = config.get('status-line')
4187+
41504188
# Extract claude-code-version configuration
41514189
claude_code_version = config.get('claude-code-version')
41524190
claude_code_version_normalized = None # Default to latest
@@ -4316,6 +4354,11 @@ def main() -> None:
43164354
# Step 13: Configure settings
43174355
print()
43184356
print(f'{Colors.CYAN}Step 13: Configuring settings...{Colors.NC}')
4357+
# Cast status_line for type safety
4358+
status_line_arg: dict[str, Any] | None = None
4359+
if status_line is not None and isinstance(status_line, dict):
4360+
status_line_arg = cast(dict[str, Any], status_line)
4361+
43194362
create_additional_settings(
43204363
hooks,
43214364
claude_user_dir,
@@ -4327,6 +4370,7 @@ def main() -> None:
43274370
always_thinking_enabled,
43284371
company_announcements,
43294372
attribution,
4373+
status_line_arg,
43304374
)
43314375

43324376
# Step 14: Create launcher script
@@ -4390,6 +4434,15 @@ def main() -> None:
43904434
print(f' * Environment variables: {len(env_variables)} configured')
43914435
if company_announcements:
43924436
print(f' * Company announcements: {len(company_announcements)} configured')
4437+
if status_line and isinstance(status_line, dict):
4438+
status_line_dict = cast(dict[str, Any], status_line)
4439+
status_line_file_val = status_line_dict.get('file', '')
4440+
if status_line_file_val and isinstance(status_line_file_val, str):
4441+
if '?' in status_line_file_val:
4442+
clean_name = Path(status_line_file_val.split('?')[0]).name
4443+
else:
4444+
clean_name = Path(status_line_file_val).name
4445+
print(f' * Status line: {clean_name}')
43934446
if os_env_variables:
43944447
set_vars = sum(1 for v in os_env_variables.values() if v is not None)
43954448
del_vars = sum(1 for v in os_env_variables.values() if v is None)

tests/test_setup_environment.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,108 @@ def test_create_additional_settings_attribution_none_not_included(self):
12591259

12601260
assert 'attribution' not in settings
12611261

1262+
def test_create_additional_settings_status_line_python(self):
1263+
"""Test statusLine with Python script."""
1264+
with tempfile.TemporaryDirectory() as tmpdir:
1265+
claude_dir = Path(tmpdir)
1266+
# Create hooks directory
1267+
hooks_dir = claude_dir / 'hooks'
1268+
hooks_dir.mkdir(parents=True, exist_ok=True)
1269+
1270+
status_line = {
1271+
'file': 'statusline.py',
1272+
'padding': 0,
1273+
}
1274+
1275+
result = setup_environment.create_additional_settings(
1276+
{},
1277+
claude_dir,
1278+
'test-env',
1279+
status_line=status_line,
1280+
)
1281+
1282+
assert result is True
1283+
settings_file = claude_dir / 'test-env-additional-settings.json'
1284+
settings = json.loads(settings_file.read_text())
1285+
1286+
assert 'statusLine' in settings
1287+
assert settings['statusLine']['type'] == 'command'
1288+
assert 'uv run' in settings['statusLine']['command']
1289+
assert 'statusline.py' in settings['statusLine']['command']
1290+
assert settings['statusLine']['padding'] == 0
1291+
1292+
def test_create_additional_settings_status_line_shell(self):
1293+
"""Test statusLine with shell script."""
1294+
with tempfile.TemporaryDirectory() as tmpdir:
1295+
claude_dir = Path(tmpdir)
1296+
hooks_dir = claude_dir / 'hooks'
1297+
hooks_dir.mkdir(parents=True, exist_ok=True)
1298+
1299+
status_line = {
1300+
'file': 'statusline.sh',
1301+
}
1302+
1303+
result = setup_environment.create_additional_settings(
1304+
{},
1305+
claude_dir,
1306+
'test-env',
1307+
status_line=status_line,
1308+
)
1309+
1310+
assert result is True
1311+
settings_file = claude_dir / 'test-env-additional-settings.json'
1312+
settings = json.loads(settings_file.read_text())
1313+
1314+
assert 'statusLine' in settings
1315+
assert settings['statusLine']['type'] == 'command'
1316+
assert 'uv run' not in settings['statusLine']['command']
1317+
assert 'statusline.sh' in settings['statusLine']['command']
1318+
assert 'padding' not in settings['statusLine']
1319+
1320+
def test_create_additional_settings_status_line_none_not_included(self):
1321+
"""Test statusLine not included when None."""
1322+
with tempfile.TemporaryDirectory() as tmpdir:
1323+
claude_dir = Path(tmpdir)
1324+
1325+
result = setup_environment.create_additional_settings(
1326+
{},
1327+
claude_dir,
1328+
'test-env',
1329+
status_line=None,
1330+
)
1331+
1332+
assert result is True
1333+
settings_file = claude_dir / 'test-env-additional-settings.json'
1334+
settings = json.loads(settings_file.read_text())
1335+
1336+
assert 'statusLine' not in settings
1337+
1338+
def test_create_additional_settings_status_line_with_query_params(self):
1339+
"""Test statusLine file with query parameters stripped."""
1340+
with tempfile.TemporaryDirectory() as tmpdir:
1341+
claude_dir = Path(tmpdir)
1342+
hooks_dir = claude_dir / 'hooks'
1343+
hooks_dir.mkdir(parents=True, exist_ok=True)
1344+
1345+
status_line = {
1346+
'file': 'statusline.py?ref_type=heads',
1347+
}
1348+
1349+
result = setup_environment.create_additional_settings(
1350+
{},
1351+
claude_dir,
1352+
'test-env',
1353+
status_line=status_line,
1354+
)
1355+
1356+
assert result is True
1357+
settings_file = claude_dir / 'test-env-additional-settings.json'
1358+
settings = json.loads(settings_file.read_text())
1359+
1360+
assert 'statusLine' in settings
1361+
assert '?' not in settings['statusLine']['command']
1362+
assert 'statusline.py' in settings['statusLine']['command']
1363+
12621364

12631365
class TestCreateLauncherScript:
12641366
"""Test launcher script creation."""

0 commit comments

Comments
 (0)