Implemented the next missing parity slice around settings slash commands.
Adds /theme, /voice, /sandbox-toggle (alias /sandbox), /keybindings, and /btw ported from npm src/commands/. Settings-touching commands write through the existing ConfigRuntime to .claude/settings.local.json (theme, voiceEnabled, sandbox.excludedCommands). /keybindings creates a JSON template on first invocation. /btw flips into a prompt-style result that asks the model to answer the side question without mutating workspace state. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+5
-5
@@ -324,7 +324,7 @@ Missing npm slash commands (from `src/commands/` — 80+ commands total):
|
|||||||
- [x] `/agents` — Inspect, create, update, and delete local agent definitions
|
- [x] `/agents` — Inspect, create, update, and delete local agent definitions
|
||||||
- [x] `/branch` — Create a branch of the current conversation
|
- [x] `/branch` — Create a branch of the current conversation
|
||||||
- [ ] `/bridge` — Connect for remote-control sessions
|
- [ ] `/bridge` — Connect for remote-control sessions
|
||||||
- [ ] `/btw` — Quick side question without interrupting main conversation
|
- [x] `/btw` — Quick side question without interrupting main conversation
|
||||||
- [x] `/chrome` — Chrome extension settings
|
- [x] `/chrome` — Chrome extension settings
|
||||||
- [x] `/color` — Set the prompt bar color for this session
|
- [x] `/color` — Set the prompt bar color for this session
|
||||||
- [x] `/compact` — Clear history but keep a summary in context
|
- [x] `/compact` — Clear history but keep a summary in context
|
||||||
@@ -343,7 +343,7 @@ Missing npm slash commands (from `src/commands/` — 80+ commands total):
|
|||||||
- [ ] `/ide` — Manage IDE integrations and show status
|
- [ ] `/ide` — Manage IDE integrations and show status
|
||||||
- [x] `/install-github-app` — Set up GitHub Actions
|
- [x] `/install-github-app` — Set up GitHub Actions
|
||||||
- [x] `/install-slack-app` — Install Slack app
|
- [x] `/install-slack-app` — Install Slack app
|
||||||
- [ ] `/keybindings` — Open keybindings config file
|
- [x] `/keybindings` — Open keybindings config file
|
||||||
- [x] `/mobile` — Mobile app store links (aliases `/ios`, `/android`)
|
- [x] `/mobile` — Mobile app store links (aliases `/ios`, `/android`)
|
||||||
- [x] `/output-style` — Deprecation pointer to `/config`
|
- [x] `/output-style` — Deprecation pointer to `/config`
|
||||||
- [x] `/passes` — Passes management
|
- [x] `/passes` — Passes management
|
||||||
@@ -358,15 +358,15 @@ Missing npm slash commands (from `src/commands/` — 80+ commands total):
|
|||||||
- [x] `/rename` — Rename current conversation
|
- [x] `/rename` — Rename current conversation
|
||||||
- [x] `/resume`, `/continue` — Resume a previous conversation
|
- [x] `/resume`, `/continue` — Resume a previous conversation
|
||||||
- [x] `/rewind`, `/checkpoint` — Restore code/conversation to a previous point
|
- [x] `/rewind`, `/checkpoint` — Restore code/conversation to a previous point
|
||||||
- [ ] `/sandbox-toggle` — Toggle sandbox mode
|
- [x] `/sandbox-toggle` — Toggle sandbox mode (alias `/sandbox`)
|
||||||
- [x] `/skills` — List available skills
|
- [x] `/skills` — List available skills
|
||||||
- [x] `/stats` — Usage statistics and activity
|
- [x] `/stats` — Usage statistics and activity
|
||||||
- [x] `/stickers` — Order stickers
|
- [x] `/stickers` — Order stickers
|
||||||
- [x] `/tag` — Toggle a searchable tag on the session
|
- [x] `/tag` — Toggle a searchable tag on the session
|
||||||
- [ ] `/theme` — Change the theme
|
- [x] `/theme` — Change the theme
|
||||||
- [x] `/upgrade` — Upgrade to Max
|
- [x] `/upgrade` — Upgrade to Max
|
||||||
- [x] `/vim` — Toggle Vim/Normal editing modes
|
- [x] `/vim` — Toggle Vim/Normal editing modes
|
||||||
- [ ] `/voice` — Toggle voice mode
|
- [x] `/voice` — Toggle voice mode
|
||||||
- [ ] Feature-gated: `/buddy`, `/fork`, `/peers`, `/proactive`, `/torch`, `/workflows` (full), etc.
|
- [ ] Feature-gated: `/buddy`, `/fork`, `/peers`, `/proactive`, `/torch`, `/workflows` (full), etc.
|
||||||
- [ ] Internal: `/backfill-sessions`, `/break-cache`, `/bughunter`, `/commit-push-pr`, `/init-verifiers`, `/mock-limits`, `/version`, `/ultraplan`, `/autofix-pr`, etc.
|
- [ ] Internal: `/backfill-sessions`, `/break-cache`, `/bughunter`, `/commit-push-pr`, `/init-verifiers`, `/mock-limits`, `/version`, `/ultraplan`, `/autofix-pr`, etc.
|
||||||
- [x] `/commit` — Create a git commit (prompt-type with injected git context)
|
- [x] `/commit` — Create a git commit (prompt-type with injected git context)
|
||||||
|
|||||||
@@ -489,6 +489,31 @@ def get_slash_command_specs() -> tuple[SlashCommandSpec, ...]:
|
|||||||
description='Reload local plugin manifests and report counts.',
|
description='Reload local plugin manifests and report counts.',
|
||||||
handler=_handle_reload_plugins,
|
handler=_handle_reload_plugins,
|
||||||
),
|
),
|
||||||
|
SlashCommandSpec(
|
||||||
|
names=('theme',),
|
||||||
|
description='List available themes or set the current theme.',
|
||||||
|
handler=_handle_theme,
|
||||||
|
),
|
||||||
|
SlashCommandSpec(
|
||||||
|
names=('voice',),
|
||||||
|
description='Toggle voice-mode setting for this workspace.',
|
||||||
|
handler=_handle_voice,
|
||||||
|
),
|
||||||
|
SlashCommandSpec(
|
||||||
|
names=('sandbox-toggle', 'sandbox'),
|
||||||
|
description='Show sandbox status or exclude a command pattern.',
|
||||||
|
handler=_handle_sandbox_toggle,
|
||||||
|
),
|
||||||
|
SlashCommandSpec(
|
||||||
|
names=('keybindings',),
|
||||||
|
description='Print or create the local keybindings file.',
|
||||||
|
handler=_handle_keybindings,
|
||||||
|
),
|
||||||
|
SlashCommandSpec(
|
||||||
|
names=('btw',),
|
||||||
|
description='Ask Claude a quick side question without altering state.',
|
||||||
|
handler=_handle_btw,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1976,6 +2001,194 @@ def _handle_reload_plugins(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_AVAILABLE_THEMES = (
|
||||||
|
'light',
|
||||||
|
'dark',
|
||||||
|
'light-daltonized',
|
||||||
|
'dark-daltonized',
|
||||||
|
'light-ansi',
|
||||||
|
'dark-ansi',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _config_get(runtime, key_path: str, default=None):
|
||||||
|
try:
|
||||||
|
return runtime.get_value(key_path)
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_theme(
|
||||||
|
agent: 'LocalCodingAgent',
|
||||||
|
args: str,
|
||||||
|
input_text: str,
|
||||||
|
) -> SlashCommandResult:
|
||||||
|
runtime = agent.config_runtime
|
||||||
|
if runtime is None:
|
||||||
|
return _local_result(input_text, 'Config runtime is unavailable.')
|
||||||
|
requested = args.strip()
|
||||||
|
current = _config_get(runtime, 'theme') or 'light'
|
||||||
|
if not requested:
|
||||||
|
lines = ['Available themes:']
|
||||||
|
for name in _AVAILABLE_THEMES:
|
||||||
|
marker = ' (current)' if name == current else ''
|
||||||
|
lines.append(f' - {name}{marker}')
|
||||||
|
lines.append('')
|
||||||
|
lines.append('Usage: /theme <name>')
|
||||||
|
return _local_result(input_text, '\n'.join(lines))
|
||||||
|
if requested not in _AVAILABLE_THEMES:
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Unknown theme "{requested}". Available: {", ".join(_AVAILABLE_THEMES)}.',
|
||||||
|
)
|
||||||
|
mutation = runtime.set_value('theme', requested, source='local')
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Theme set to {requested} (saved to {mutation.store_path}).',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_voice(
|
||||||
|
agent: 'LocalCodingAgent',
|
||||||
|
args: str,
|
||||||
|
input_text: str,
|
||||||
|
) -> SlashCommandResult:
|
||||||
|
runtime = agent.config_runtime
|
||||||
|
if runtime is None:
|
||||||
|
return _local_result(input_text, 'Config runtime is unavailable.')
|
||||||
|
arg = args.strip().lower()
|
||||||
|
current = bool(_config_get(runtime, 'voiceEnabled', False))
|
||||||
|
if arg in {'on', 'enable', 'true'}:
|
||||||
|
new_value = True
|
||||||
|
elif arg in {'off', 'disable', 'false'}:
|
||||||
|
new_value = False
|
||||||
|
elif not arg:
|
||||||
|
new_value = not current
|
||||||
|
else:
|
||||||
|
return _local_result(input_text, 'Usage: /voice [on|off]')
|
||||||
|
mutation = runtime.set_value('voiceEnabled', new_value, source='local')
|
||||||
|
state = 'enabled' if new_value else 'disabled'
|
||||||
|
extra = ''
|
||||||
|
if new_value:
|
||||||
|
import platform
|
||||||
|
|
||||||
|
if platform.system() == 'Linux':
|
||||||
|
extra = (
|
||||||
|
'\nLinux note: confirm microphone access in your audio settings '
|
||||||
|
'before holding to talk.'
|
||||||
|
)
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Voice mode {state} (saved to {mutation.store_path}).{extra}',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_sandbox_toggle(
|
||||||
|
agent: 'LocalCodingAgent',
|
||||||
|
args: str,
|
||||||
|
input_text: str,
|
||||||
|
) -> SlashCommandResult:
|
||||||
|
runtime = agent.config_runtime
|
||||||
|
if runtime is None:
|
||||||
|
return _local_result(input_text, 'Config runtime is unavailable.')
|
||||||
|
trimmed = args.strip()
|
||||||
|
if not trimmed:
|
||||||
|
enabled = bool(_config_get(runtime, 'sandbox.enabled', False))
|
||||||
|
excluded = _config_get(runtime, 'sandbox.excludedCommands') or []
|
||||||
|
lines = [
|
||||||
|
f'Sandbox: {"enabled" if enabled else "disabled"}',
|
||||||
|
f'Excluded commands ({len(excluded)}):',
|
||||||
|
]
|
||||||
|
for pattern in excluded:
|
||||||
|
lines.append(f' - {pattern}')
|
||||||
|
lines.append('')
|
||||||
|
lines.append(
|
||||||
|
'Usage: /sandbox-toggle exclude "<pattern>" '
|
||||||
|
'— append a command pattern to the local sandbox excludes.'
|
||||||
|
)
|
||||||
|
return _local_result(input_text, '\n'.join(lines))
|
||||||
|
|
||||||
|
parts = trimmed.split(None, 1)
|
||||||
|
subcommand = parts[0].lower()
|
||||||
|
if subcommand != 'exclude':
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Unknown subcommand "{subcommand}". Available: exclude.',
|
||||||
|
)
|
||||||
|
if len(parts) < 2 or not parts[1].strip():
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
'Usage: /sandbox-toggle exclude "<pattern>" '
|
||||||
|
'(e.g., /sandbox-toggle exclude "npm run test:*").',
|
||||||
|
)
|
||||||
|
pattern = parts[1].strip().strip('"').strip("'")
|
||||||
|
existing = list(_config_get(runtime, 'sandbox.excludedCommands') or [])
|
||||||
|
if pattern in existing:
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Pattern "{pattern}" is already in sandbox.excludedCommands.',
|
||||||
|
)
|
||||||
|
existing.append(pattern)
|
||||||
|
mutation = runtime.set_value(
|
||||||
|
'sandbox.excludedCommands', existing, source='local',
|
||||||
|
)
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'Added "{pattern}" to sandbox.excludedCommands in {mutation.store_path}.',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_KEYBINDINGS_TEMPLATE = (
|
||||||
|
'{\n'
|
||||||
|
' "$schema": "https://claude.ai/schemas/keybindings.json",\n'
|
||||||
|
' "bindings": {\n'
|
||||||
|
' // "chat:submit": "ctrl+enter"\n'
|
||||||
|
' }\n'
|
||||||
|
'}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_keybindings(
|
||||||
|
agent: 'LocalCodingAgent',
|
||||||
|
_args: str,
|
||||||
|
input_text: str,
|
||||||
|
) -> SlashCommandResult:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
cwd = Path(agent.runtime_config.cwd)
|
||||||
|
path = cwd / '.claude' / 'keybindings.json'
|
||||||
|
created = False
|
||||||
|
if not path.exists():
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(_KEYBINDINGS_TEMPLATE, encoding='utf-8')
|
||||||
|
created = True
|
||||||
|
verb = 'Created' if created else 'Found'
|
||||||
|
import os
|
||||||
|
|
||||||
|
editor = os.environ.get('EDITOR') or os.environ.get('VISUAL') or '<your editor>'
|
||||||
|
return _local_result(
|
||||||
|
input_text,
|
||||||
|
f'{verb} {path}.\n'
|
||||||
|
f'Edit it with: {editor} {path}',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_btw(
|
||||||
|
agent: 'LocalCodingAgent',
|
||||||
|
args: str,
|
||||||
|
input_text: str,
|
||||||
|
) -> SlashCommandResult:
|
||||||
|
question = args.strip()
|
||||||
|
if not question:
|
||||||
|
return _local_result(input_text, 'Usage: /btw <your question>')
|
||||||
|
prompt = (
|
||||||
|
'Answer the following side question concisely. Do NOT modify any files, '
|
||||||
|
"run shell commands, or alter the workspace — just answer in text.\n\n"
|
||||||
|
f'Side question: {question}'
|
||||||
|
)
|
||||||
|
return _prompt_result(input_text, prompt)
|
||||||
|
|
||||||
|
|
||||||
def _prompt_result(input_text: str, prompt: str) -> SlashCommandResult:
|
def _prompt_result(input_text: str, prompt: str) -> SlashCommandResult:
|
||||||
"""Return a prompt-type result — the prompt gets sent to the model."""
|
"""Return a prompt-type result — the prompt gets sent to the model."""
|
||||||
return SlashCommandResult(
|
return SlashCommandResult(
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
"""Tests for settings-touching slash commands ported from the npm source.
|
||||||
|
|
||||||
|
Covers /theme, /voice, /sandbox-toggle (alias /sandbox), /keybindings, /btw.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.agent_runtime import LocalCodingAgent
|
||||||
|
from src.agent_types import AgentRuntimeConfig, ModelConfig
|
||||||
|
|
||||||
|
|
||||||
|
def _make_agent(tmp_dir: str) -> LocalCodingAgent:
|
||||||
|
return LocalCodingAgent(
|
||||||
|
model_config=ModelConfig(model='test-model'),
|
||||||
|
runtime_config=AgentRuntimeConfig(cwd=Path(tmp_dir)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _local_settings(tmp_dir: str) -> dict:
|
||||||
|
path = Path(tmp_dir) / '.claude' / 'settings.local.json'
|
||||||
|
if not path.exists():
|
||||||
|
return {}
|
||||||
|
return json.loads(path.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeCommandTest(unittest.TestCase):
|
||||||
|
def test_lists_themes_when_no_arg(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/theme').final_output
|
||||||
|
self.assertIn('Available themes', out)
|
||||||
|
self.assertIn('light', out)
|
||||||
|
self.assertIn('dark', out)
|
||||||
|
self.assertIn('Usage: /theme <name>', out)
|
||||||
|
|
||||||
|
def test_rejects_unknown_theme(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/theme neon').final_output
|
||||||
|
self.assertIn('Unknown theme', out)
|
||||||
|
self.assertIn('neon', out)
|
||||||
|
|
||||||
|
def test_sets_theme_and_persists(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/theme dark').final_output
|
||||||
|
self.assertIn('Theme set to dark', out)
|
||||||
|
settings = _local_settings(tmp)
|
||||||
|
self.assertEqual(settings.get('theme'), 'dark')
|
||||||
|
|
||||||
|
def test_marks_current_theme(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
agent.run('/theme dark')
|
||||||
|
out = agent.run('/theme').final_output
|
||||||
|
self.assertIn('dark (current)', out)
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceCommandTest(unittest.TestCase):
|
||||||
|
def test_toggle_enables_when_unset(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/voice').final_output
|
||||||
|
self.assertIn('Voice mode enabled', out)
|
||||||
|
self.assertEqual(_local_settings(tmp).get('voiceEnabled'), True)
|
||||||
|
|
||||||
|
def test_toggle_disables_when_enabled(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
agent.run('/voice on')
|
||||||
|
out = agent.run('/voice').final_output
|
||||||
|
self.assertIn('Voice mode disabled', out)
|
||||||
|
self.assertEqual(_local_settings(tmp).get('voiceEnabled'), False)
|
||||||
|
|
||||||
|
def test_explicit_on_off(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
self.assertIn('enabled', agent.run('/voice on').final_output)
|
||||||
|
self.assertIn('disabled', agent.run('/voice off').final_output)
|
||||||
|
|
||||||
|
def test_rejects_unknown_arg(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/voice maybe').final_output
|
||||||
|
self.assertIn('Usage', out)
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxToggleCommandTest(unittest.TestCase):
|
||||||
|
def test_status_with_no_args(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/sandbox-toggle').final_output
|
||||||
|
self.assertIn('Sandbox:', out)
|
||||||
|
self.assertIn('Excluded commands', out)
|
||||||
|
self.assertIn('Usage:', out)
|
||||||
|
|
||||||
|
def test_alias_sandbox(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/sandbox').final_output
|
||||||
|
self.assertIn('Sandbox:', out)
|
||||||
|
|
||||||
|
def test_exclude_appends_pattern(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/sandbox-toggle exclude "npm run test:*"').final_output
|
||||||
|
self.assertIn('Added "npm run test:*"', out)
|
||||||
|
settings = _local_settings(tmp)
|
||||||
|
excluded = settings.get('sandbox', {}).get('excludedCommands', [])
|
||||||
|
self.assertIn('npm run test:*', excluded)
|
||||||
|
|
||||||
|
def test_exclude_dedupes(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
agent.run('/sandbox-toggle exclude "rm -rf /"')
|
||||||
|
out = agent.run('/sandbox-toggle exclude "rm -rf /"').final_output
|
||||||
|
self.assertIn('already in', out)
|
||||||
|
|
||||||
|
def test_exclude_requires_pattern(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/sandbox-toggle exclude').final_output
|
||||||
|
self.assertIn('Usage', out)
|
||||||
|
|
||||||
|
def test_unknown_subcommand(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/sandbox-toggle wat').final_output
|
||||||
|
self.assertIn('Unknown subcommand', out)
|
||||||
|
|
||||||
|
|
||||||
|
class KeybindingsCommandTest(unittest.TestCase):
|
||||||
|
def test_creates_template_when_missing(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
out = agent.run('/keybindings').final_output
|
||||||
|
path = Path(tmp) / '.claude' / 'keybindings.json'
|
||||||
|
self.assertTrue(path.exists())
|
||||||
|
self.assertIn('Created', out)
|
||||||
|
self.assertIn(str(path), out)
|
||||||
|
# Template is valid JSON-ish (has braces); strict json.loads would
|
||||||
|
# choke on the "//" comment, so just sanity-check structure.
|
||||||
|
text = path.read_text(encoding='utf-8')
|
||||||
|
self.assertIn('"bindings"', text)
|
||||||
|
|
||||||
|
def test_reports_existing_file(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
agent.run('/keybindings')
|
||||||
|
out = agent.run('/keybindings').final_output
|
||||||
|
self.assertIn('Found', out)
|
||||||
|
|
||||||
|
|
||||||
|
class BtwCommandTest(unittest.TestCase):
|
||||||
|
def test_no_question_shows_usage(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
result = agent.run('/btw').final_output
|
||||||
|
self.assertIn('Usage: /btw', result)
|
||||||
|
|
||||||
|
def test_question_returns_prompt_result(self) -> None:
|
||||||
|
from src.agent_slash_commands import preprocess_slash_command
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
agent = _make_agent(tmp)
|
||||||
|
result = preprocess_slash_command(agent, '/btw what does this codebase do?')
|
||||||
|
self.assertTrue(result.handled)
|
||||||
|
self.assertTrue(result.should_query)
|
||||||
|
self.assertIn('side question', (result.prompt or '').lower())
|
||||||
|
self.assertIn('what does this codebase do?', result.prompt or '')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user