feat(install): configure custom status line

This commit is contained in:
fraujulian committed 2026-09-24 22:58:56 +02:00
1 parent 0d034f4299
commit ee19c64beb
14 files changed
+101 -315

No files matched your search

+13 -71
View File
@@ -40,71 +40,6 @@ class InstallOptionsTests(unittest.TestCase):
self.assertEqual(result['hooks']['Stop'][0]['hooks'], [{'command': 'echo keep'}])
self.assertEqual(result['tui']['status_line'], ['model'])
def test_native_option_helpers_select_clients_and_restore_environment(self):
shells = [('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))]
for kind, executable in shells:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=kind):
if kind == 'powershell':
command = [executable, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $false }; function Test-GlobalNpmCodex { $false }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) "
"Write-Output ($Command + ':' + ($Arguments -join ' ')) }; "
"$env:CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE='original'; "
"Update-SelectedAgentClis -Client Both; "
"if ($env:CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE -ne 'original') { throw 'Environment leaked' }"]
else:
command = [executable, '-c',
'. ./scripts/lib/install-options.sh; '
'codex_process_active() { return 1; }; global_npm_codex() { return 1; }; '
'run_plugin_command() { shift; printf "%s:%s\\n" "$1" "$2"; }; '
'client=both; dry_run=false; summary=false; '
'CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=original; update_selected_agent_clis; '
'[ "$CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE" = original ]']
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(result.stdout.splitlines(), ['codex:update', 'claude:update'])
def test_running_codex_is_skipped_and_npm_install_is_used_when_available(self):
shell = shutil.which('powershell') or shutil.which('pwsh')
if shell:
skipped = subprocess.run([
shell, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $true }; function Test-GlobalNpmCodex { $true }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) Write-Output $Command }; "
"Update-SelectedAgentClis -Client Both"
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(skipped.returncode, 0, skipped.stdout + skipped.stderr)
self.assertEqual(skipped.stdout.splitlines()[-1], 'claude')
self.assertIn('Codex CLI update skipped', skipped.stdout + skipped.stderr)
npm = subprocess.run([
shell, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $false }; function Test-GlobalNpmCodex { $true }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) "
"Write-Output ($Command + ':' + ($Arguments -join ' ')) }; Update-SelectedAgentClis -Client Codex"
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(npm.returncode, 0, npm.stdout + npm.stderr)
self.assertEqual(npm.stdout.strip(), 'npm:install -g @openai/codex@latest')
bash = (str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))
if bash and Path(bash).exists():
result = subprocess.run([
bash, '-c',
'. ./scripts/lib/install-options.sh; codex_process_active() { return 0; }; '
'global_npm_codex() { return 0; }; run_plugin_command() { shift; printf "%s\\n" "$1"; }; '
'client=both; dry_run=false; summary=false; update_selected_agent_clis'
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(result.stdout.splitlines(), ['claude'])
self.assertIn('Codex CLI update skipped', result.stderr)
def test_flashbang_toggle_is_applied_by_both_manifest_writers(self):
shells = [('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
@@ -165,8 +100,13 @@ class InstallOptionsTests(unittest.TestCase):
package = root / 'generated' / (client + '-bash')
(package / 'skills').mkdir(parents=True)
(package / 'skills' / 'test.txt').write_text('managed')
if client == 'claude':
(package / 'statusline').mkdir()
(package / 'statusline' / 'statusline.sh').write_text('managed status line')
config = ('[[hooks.Stop]]\n[[hooks.Stop.hooks]]\ncommand = \'bash "__AI_CONFIG_ROOT__/flashbang.sh"\'\n'
if client == 'codex' else '{"hooks":{"Stop":[{"hooks":[{"command":"bash flashbang.sh"}]}]}}')
'[tui]\nstatus_line = ["model"]\n' if client == 'codex' else
'{"hooks":{"Stop":[{"hooks":[{"command":"bash flashbang.sh"}]}]},'
'"statusLine":{"type":"command","command":"bash statusline.sh"}}')
(package / ('config.toml' if client == 'codex' else 'settings.json')).write_text(config)
suffix = 'ps1' if kind == 'powershell' else 'sh'
for name in ('install', 'update'):
@@ -186,7 +126,7 @@ class InstallOptionsTests(unittest.TestCase):
'select_configured_plugins() { :; }\nsync_configured_plugins() { :; }\n'
'run_plugin_command() { printf "CLI-MOCK %s\\n" "$2"; }\n')
environment = dict(os.environ, HOME=home.as_posix(), TEST_INSTALL_HOME=str(home))
for name, quick, answers in [('install', False, 'y\nn\nn\nn\n'), ('update', True, '')]:
for name, quick, answers in [('install', False, 'n\nn\nn\n'), ('update', True, '')]:
entry = str(commands / (name + '.' + suffix))
command = ([executable, '-NoProfile', '-File', entry, '-Shell', 'Bash', '-Client', 'Both', '-Summary']
if kind == 'powershell' else [executable, entry, '--shell', 'bash', '--client', 'both', '--summary'])
@@ -194,16 +134,18 @@ class InstallOptionsTests(unittest.TestCase):
command.append('-Quick' if kind == 'powershell' else '--quick')
result = subprocess.run(command, env=environment, input=answers, capture_output=True, text=True, timeout=25)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertTrue('CLI-MOCK codex' in result.stdout or
'Codex CLI update skipped' in result.stdout + result.stderr)
self.assertIn('CLI-MOCK claude', result.stdout)
self.assertNotIn('CLI-MOCK', result.stdout)
if quick:
self.assertNotIn('Enable the Flashbang', result.stdout + result.stderr)
self.assertNotIn('Apply the custom status line', result.stdout + result.stderr)
state = json.loads((home / '.my-ai-configuration/selection.json').read_text())
self.assertTrue(state['update_agents'])
self.assertFalse(state['flashbang'])
self.assertFalse(state['statusline'])
self.assertEqual(state['shell'], 'bash')
self.assertNotIn('Stop', json.loads((home / '.claude/settings.json').read_text()).get('hooks', {}))
self.assertNotIn('statusLine', json.loads((home / '.claude/settings.json').read_text()))
self.assertFalse((home / '.claude/statusline/statusline.sh').exists())
self.assertNotIn('status_line', tomllib.loads((home / '.codex/config.toml').read_text()).get('tui', {}))
if __name__ == '__main__':
+9 -8
View File
@@ -25,7 +25,7 @@ class SelectionTests(unittest.TestCase):
capture_output=True, text=True, timeout=10)
def save(self, *arguments):
result = self.run_state('write', '--shell', 'powershell', '--update-agents', 'false', '--flashbang', 'false', '--client', 'both', *arguments)
result = self.run_state('write', '--shell', 'powershell', '--flashbang', 'false', '--statusline', 'false', '--client', 'both', *arguments)
self.assertEqual(result.returncode, 0, result.stderr)
def test_missing_selection_fails_without_creating_state(self):
@@ -59,30 +59,31 @@ class SelectionTests(unittest.TestCase):
self.assertIn('without Quick', result.stderr)
migrated = json.loads(self.run_state('read', '--allow-legacy').stdout)
self.assertEqual(migrated['shell'], 'powershell')
self.assertFalse(migrated['update_agents'])
self.assertTrue(migrated['flashbang'])
self.assertTrue(migrated['statusline'])
def test_boolean_options_are_required_and_preserved(self):
self.save()
state = json.loads(self.run_state('read').stdout)
self.assertFalse(state['update_agents'])
self.assertFalse(state['flashbang'])
self.assertFalse(state['statusline'])
state['flashbang'] = 'false'
self.path.write_text(json.dumps(state))
self.assertNotEqual(self.run_state('read').returncode, 0)
def test_semantic_retrieval_option_round_trips(self):
result = self.run_state('write', '--shell', 'bash', '--client', 'both', '--update-agents', 'false',
'--flashbang', 'true', '--semantic-retrieval', 'true')
result = self.run_state('write', '--shell', 'bash', '--client', 'both',
'--flashbang', 'true', '--statusline', 'true', '--semantic-retrieval', 'true')
self.assertEqual(result.returncode, 0, result.stderr)
state = json.loads(self.run_state('read').stdout)
self.assertTrue(state['semantic_retrieval'])
self.assertTrue(state['statusline'])
self.assertIn('semantic_retrieval\ttrue', self.run_state('read', '--format', 'tsv').stdout)
def test_invalid_write_preserves_last_selection(self):
self.save('--selected', 'Superpowers')
original = self.path.read_bytes()
result = self.run_state('write', '--shell', 'bash', '--update-agents', 'false', '--flashbang', 'true', '--client', 'codex', '--selected', 'Unknown')
result = self.run_state('write', '--shell', 'bash', '--flashbang', 'true', '--client', 'codex', '--selected', 'Unknown')
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.path.read_bytes(), original)
@@ -108,7 +109,7 @@ class SelectionTests(unittest.TestCase):
save = subprocess.run([shell, '-NoProfile', '-Command',
"$ErrorActionPreference = 'Stop'; . (Join-Path $env:TEST_REPOSITORY_ROOT 'scripts/lib/selection-state.ps1'); "
"Save-UpdateSelection -HomePath $env:TEST_SELECTION_HOME -RepositoryRoot $env:TEST_REPOSITORY_ROOT "
"-Shell PowerShell -Client Both -UpdateAgents $false -Flashbang $false -Plugins @{ Selected = @([pscustomobject]@{name='Superpowers'}); Deselected = @() }"],
"-Shell PowerShell -Client Both -Flashbang $false -StatusLine $false -Plugins @{ Selected = @([pscustomobject]@{name='Superpowers'}); Deselected = @() }"],
env=environment, capture_output=True, text=True, timeout=10)
self.assertEqual(save.returncode, 0, save.stdout + save.stderr)
original = self.path.read_bytes()
@@ -140,7 +141,7 @@ class SelectionTests(unittest.TestCase):
if not shell:
self.skipTest('Bash unavailable')
save = subprocess.run([shell, '-c',
'set -euo pipefail; root=$1; home_path=$2; shell=powershell; client=both; update_agents=false; flashbang=false; '
'set -euo pipefail; root=$1; home_path=$2; shell=powershell; client=both; flashbang=false; statusline=false; '
'selected_plugins=(Superpowers); deselected_plugins=(); '
'. "$root/scripts/lib/selection-state.sh"; save_update_selection',
'test', ROOT.as_posix(), self.home.as_posix()],