Implemented the next missing parity slice around informational slash commands.
Adds 15 commands ported from npm src/commands/: /output-style, /release-notes, /feedback (alias /bug), /upgrade, /stickers, /mobile (aliases /ios, /android), /desktop (alias /app), /install-github-app, /install-slack-app, /privacy-settings, /extra-usage, /passes, /rate-limit-options, /chrome, and /reload-plugins. Browser launches honor CI / CLAUDE_CODE_NO_BROWSER and fall back to a printed link when a browser is unavailable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+15
-15
@@ -325,34 +325,34 @@ Missing npm slash commands (from `src/commands/` — 80+ commands total):
|
||||
- [x] `/branch` — Create a branch of the current conversation
|
||||
- [ ] `/bridge` — Connect for remote-control sessions
|
||||
- [ ] `/btw` — Quick side question without interrupting main conversation
|
||||
- [ ] `/chrome` — Chrome extension settings
|
||||
- [x] `/chrome` — Chrome extension settings
|
||||
- [x] `/color` — Set the prompt bar color for this session
|
||||
- [x] `/compact` — Clear history but keep a summary in context
|
||||
- [x] `/copy` — Copy Claude's last response to clipboard
|
||||
- [x] `/cost` — Show total cost and duration of session
|
||||
- [ ] `/desktop` — Continue session in Claude Desktop
|
||||
- [x] `/desktop` — Continue session in Claude Desktop
|
||||
- [x] `/diff` — View uncommitted changes and per-turn diffs
|
||||
- [x] `/doctor` — Diagnose and verify installation and settings
|
||||
- [x] `/effort` — Set effort level for model usage
|
||||
- [x] `/exit` — Exit the REPL
|
||||
- [x] `/export` — Export conversation to file or clipboard
|
||||
- [ ] `/extra-usage` — Configure extra usage for rate limits
|
||||
- [x] `/extra-usage` — Configure extra usage for rate limits
|
||||
- [x] `/fast` — Toggle fast mode
|
||||
- [ ] `/feedback` — Submit feedback
|
||||
- [x] `/feedback` — Submit feedback (alias `/bug`)
|
||||
- [x] `/files` — List all files currently in context
|
||||
- [ ] `/ide` — Manage IDE integrations and show status
|
||||
- [ ] `/install-github-app` — Set up GitHub Actions
|
||||
- [ ] `/install-slack-app` — Install Slack app
|
||||
- [x] `/install-github-app` — Set up GitHub Actions
|
||||
- [x] `/install-slack-app` — Install Slack app
|
||||
- [ ] `/keybindings` — Open keybindings config file
|
||||
- [ ] `/mobile` — QR code for mobile app
|
||||
- [ ] `/output-style` — Change output style
|
||||
- [ ] `/passes` — Passes management
|
||||
- [x] `/mobile` — Mobile app store links (aliases `/ios`, `/android`)
|
||||
- [x] `/output-style` — Deprecation pointer to `/config`
|
||||
- [x] `/passes` — Passes management
|
||||
- [ ] `/plugin` — Plugin management
|
||||
- [x] `/pr-comments`, `/pr_comments` — Get comments from a GitHub PR (prompt-type)
|
||||
- [ ] `/privacy-settings` — View/update privacy settings
|
||||
- [ ] `/rate-limit-options` — Show options when rate limited
|
||||
- [ ] `/release-notes` — View release notes
|
||||
- [ ] `/reload-plugins` — Activate pending plugin changes
|
||||
- [x] `/privacy-settings` — View/update privacy settings
|
||||
- [x] `/rate-limit-options` — Show options when rate limited
|
||||
- [x] `/release-notes` — View release notes
|
||||
- [x] `/reload-plugins` — Activate pending plugin changes
|
||||
- [ ] `/remote-env` — Configure default remote environment
|
||||
- [ ] `/remote-setup` — Remote setup configuration
|
||||
- [x] `/rename` — Rename current conversation
|
||||
@@ -361,10 +361,10 @@ Missing npm slash commands (from `src/commands/` — 80+ commands total):
|
||||
- [ ] `/sandbox-toggle` — Toggle sandbox mode
|
||||
- [x] `/skills` — List available skills
|
||||
- [x] `/stats` — Usage statistics and activity
|
||||
- [ ] `/stickers` — Order stickers
|
||||
- [x] `/stickers` — Order stickers
|
||||
- [x] `/tag` — Toggle a searchable tag on the session
|
||||
- [ ] `/theme` — Change the theme
|
||||
- [ ] `/upgrade` — Upgrade to Max
|
||||
- [x] `/upgrade` — Upgrade to Max
|
||||
- [x] `/vim` — Toggle Vim/Normal editing modes
|
||||
- [ ] `/voice` — Toggle voice mode
|
||||
- [ ] Feature-gated: `/buddy`, `/fork`, `/peers`, `/proactive`, `/torch`, `/workflows` (full), etc.
|
||||
|
||||
@@ -414,6 +414,81 @@ def get_slash_command_specs() -> tuple[SlashCommandSpec, ...]:
|
||||
description='Restore the conversation to a previous point.',
|
||||
handler=_handle_rewind,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('output-style',),
|
||||
description='Deprecated: use /config to change your output style.',
|
||||
handler=_handle_output_style,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('release-notes',),
|
||||
description='Show local release notes or a link to the changelog.',
|
||||
handler=_handle_release_notes,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('feedback', 'bug'),
|
||||
description='Open the Claude Code feedback page in a browser.',
|
||||
handler=_handle_feedback,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('upgrade',),
|
||||
description='Open the Claude.ai upgrade page in a browser.',
|
||||
handler=_handle_upgrade,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('stickers',),
|
||||
description='Open the Claude Code sticker order page in a browser.',
|
||||
handler=_handle_stickers,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('mobile', 'ios', 'android'),
|
||||
description='Show download links for the Claude mobile apps.',
|
||||
handler=_handle_mobile,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('desktop', 'app'),
|
||||
description='Show the Claude Desktop handoff page link.',
|
||||
handler=_handle_desktop,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('install-github-app',),
|
||||
description='Open the Claude GitHub Actions setup page.',
|
||||
handler=_handle_install_github_app,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('install-slack-app',),
|
||||
description='Open the Claude Slack app installation page.',
|
||||
handler=_handle_install_slack_app,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('privacy-settings',),
|
||||
description='Open the Claude.ai privacy controls page.',
|
||||
handler=_handle_privacy_settings,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('extra-usage',),
|
||||
description='Show extra-usage configuration link.',
|
||||
handler=_handle_extra_usage,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('passes',),
|
||||
description='Show Claude Code guest passes information.',
|
||||
handler=_handle_passes,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('rate-limit-options',),
|
||||
description='Show options when the active account hits a rate limit.',
|
||||
handler=_handle_rate_limit_options,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('chrome',),
|
||||
description='Open the Claude Chrome extension page.',
|
||||
handler=_handle_chrome,
|
||||
),
|
||||
SlashCommandSpec(
|
||||
names=('reload-plugins',),
|
||||
description='Reload local plugin manifests and report counts.',
|
||||
handler=_handle_reload_plugins,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1616,6 +1691,291 @@ def _handle_rewind(agent: 'LocalCodingAgent', args: str, input_text: str) -> Sla
|
||||
)
|
||||
|
||||
|
||||
_FEEDBACK_URL = 'https://github.com/anthropics/claude-code/issues'
|
||||
_UPGRADE_URL = 'https://claude.ai/upgrade/max'
|
||||
_STICKERS_URL = 'https://www.stickermule.com/claudecode'
|
||||
_MOBILE_IOS_URL = 'https://apps.apple.com/app/claude-by-anthropic/id6473753684'
|
||||
_MOBILE_ANDROID_URL = 'https://play.google.com/store/apps/details?id=com.anthropic.claude'
|
||||
_DESKTOP_URL = 'https://claude.ai/download'
|
||||
_GITHUB_APP_URL = 'https://github.com/apps/claude'
|
||||
_SLACK_APP_URL = 'https://slack.com/marketplace/A08SF47R6P4-claude'
|
||||
_PRIVACY_URL = 'https://claude.ai/settings/data-privacy-controls'
|
||||
_CHROME_EXTENSION_URL = 'https://claude.ai/chrome'
|
||||
_CHANGELOG_URL = 'https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md'
|
||||
|
||||
|
||||
def _try_open_browser(url: str) -> bool:
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
# Avoid spawning a browser in CI / non-interactive environments.
|
||||
if os.environ.get('CLAUDE_CODE_NO_BROWSER') or os.environ.get('CI'):
|
||||
return False
|
||||
try:
|
||||
return webbrowser.open(url, new=2)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _open_or_link(url: str, *, opening_message: str, fallback_message: str) -> str:
|
||||
if _try_open_browser(url):
|
||||
return f'{opening_message}\n {url}'
|
||||
return f'{fallback_message}\n {url}'
|
||||
|
||||
|
||||
def _changelog_path(agent: 'LocalCodingAgent') -> 'Path':
|
||||
from pathlib import Path
|
||||
|
||||
cwd = Path(agent.runtime_config.cwd)
|
||||
for candidate in (cwd / 'CHANGELOG.md', cwd / 'docs' / 'CHANGELOG.md'):
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
return cwd / 'CHANGELOG.md'
|
||||
|
||||
|
||||
def _handle_output_style(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
'/output-style has been deprecated. Use /config to change your output style, '
|
||||
'or set it in your settings file. Changes take effect on the next session.',
|
||||
)
|
||||
|
||||
|
||||
def _handle_release_notes(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
path = _changelog_path(agent)
|
||||
if path.exists():
|
||||
try:
|
||||
content = path.read_text(encoding='utf-8').strip()
|
||||
except OSError as exc:
|
||||
return _local_result(input_text, f'Could not read {path}: {exc}')
|
||||
# Show only the most recent release block (everything up to the second
|
||||
# second-level heading, mirroring how the npm command surfaces a single
|
||||
# version chunk by default).
|
||||
lines = content.splitlines()
|
||||
chunk: list[str] = []
|
||||
seen_heading = False
|
||||
for line in lines:
|
||||
if line.startswith('## '):
|
||||
if seen_heading:
|
||||
break
|
||||
seen_heading = True
|
||||
chunk.append(line)
|
||||
return _local_result(input_text, '\n'.join(chunk).strip() or content)
|
||||
return _local_result(
|
||||
input_text,
|
||||
f'No local CHANGELOG.md found. See the full changelog at:\n {_CHANGELOG_URL}',
|
||||
)
|
||||
|
||||
|
||||
def _handle_feedback(
|
||||
agent: 'LocalCodingAgent',
|
||||
args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
note = args.strip()
|
||||
body = _open_or_link(
|
||||
_FEEDBACK_URL,
|
||||
opening_message='Opening the Claude Code feedback tracker in your browser…',
|
||||
fallback_message='Submit feedback at:',
|
||||
)
|
||||
if note:
|
||||
body += f'\n\nDraft note (copy into the report form):\n{note}'
|
||||
return _local_result(input_text, body)
|
||||
|
||||
|
||||
def _handle_upgrade(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_UPGRADE_URL,
|
||||
opening_message='Opening the Claude.ai upgrade page in your browser…',
|
||||
fallback_message='Upgrade your account at:',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_stickers(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_STICKERS_URL,
|
||||
opening_message='Opening the Claude Code sticker page in your browser…',
|
||||
fallback_message='Order Claude Code stickers at:',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_mobile(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
lines = [
|
||||
'Download the Claude mobile app:',
|
||||
f' iOS: {_MOBILE_IOS_URL}',
|
||||
f' Android: {_MOBILE_ANDROID_URL}',
|
||||
]
|
||||
return _local_result(input_text, '\n'.join(lines))
|
||||
|
||||
|
||||
def _handle_desktop(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
import platform
|
||||
|
||||
system = platform.system()
|
||||
if system not in {'Darwin', 'Windows'}:
|
||||
return _local_result(
|
||||
input_text,
|
||||
f'Claude Desktop is currently available on macOS and Windows only '
|
||||
f'(detected: {system}). Download:\n {_DESKTOP_URL}',
|
||||
)
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_DESKTOP_URL,
|
||||
opening_message='Opening the Claude Desktop download page in your browser…',
|
||||
fallback_message='Download Claude Desktop at:',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_install_github_app(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_GITHUB_APP_URL,
|
||||
opening_message='Opening the Claude GitHub App installation page in your browser…',
|
||||
fallback_message='Set up Claude GitHub Actions at:',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_install_slack_app(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_SLACK_APP_URL,
|
||||
opening_message='Opening the Claude Slack app marketplace page in your browser…',
|
||||
fallback_message="Couldn't open browser. Visit:",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_privacy_settings(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
f'Review and manage your privacy settings at:\n {_PRIVACY_URL}',
|
||||
)
|
||||
|
||||
|
||||
def _handle_extra_usage(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
'Configure extra usage on a Claude.ai account at:\n'
|
||||
f' {_UPGRADE_URL}\n'
|
||||
'After upgrading, run /login to refresh your local credentials.',
|
||||
)
|
||||
|
||||
|
||||
def _handle_passes(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
'Claude Code guest passes are managed in your Claude.ai account.\n'
|
||||
' Visit https://claude.ai to sign in and view remaining passes.',
|
||||
)
|
||||
|
||||
|
||||
def _handle_rate_limit_options(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
lines = [
|
||||
'When the current account hits a rate limit, you can:',
|
||||
' - Run /upgrade to move to a higher Claude.ai plan.',
|
||||
' - Run /extra-usage to enable per-message billing on a Claude.ai plan.',
|
||||
' - Run /login to switch to an API-key billed account.',
|
||||
f'See {_UPGRADE_URL} for plan details.',
|
||||
]
|
||||
return _local_result(input_text, '\n'.join(lines))
|
||||
|
||||
|
||||
def _handle_chrome(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
return _local_result(
|
||||
input_text,
|
||||
_open_or_link(
|
||||
_CHROME_EXTENSION_URL,
|
||||
opening_message='Opening the Claude Chrome extension page in your browser…',
|
||||
fallback_message='Install the Claude Chrome extension at:',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _handle_reload_plugins(
|
||||
agent: 'LocalCodingAgent',
|
||||
_args: str,
|
||||
input_text: str,
|
||||
) -> SlashCommandResult:
|
||||
from pathlib import Path
|
||||
from .plugin_runtime import PluginRuntime
|
||||
|
||||
runtime = PluginRuntime.from_workspace(Path(agent.runtime_config.cwd))
|
||||
agent.plugin_runtime = runtime
|
||||
plugin_count = len(runtime.manifests)
|
||||
tool_count = sum(len(manifest.tool_names) for manifest in runtime.manifests)
|
||||
hook_count = sum(len(manifest.hook_names) for manifest in runtime.manifests)
|
||||
virtual_count = sum(len(manifest.virtual_tools) for manifest in runtime.manifests)
|
||||
return _local_result(
|
||||
input_text,
|
||||
'Reloaded plugins: '
|
||||
f'{plugin_count} plugin(s) · {tool_count} tool(s) · {hook_count} hook(s) · '
|
||||
f'{virtual_count} virtual tool(s)',
|
||||
)
|
||||
|
||||
|
||||
def _prompt_result(input_text: str, prompt: str) -> SlashCommandResult:
|
||||
"""Return a prompt-type result — the prompt gets sent to the model."""
|
||||
return SlashCommandResult(
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Tests for the informational slash commands ported from the npm source.
|
||||
|
||||
Covers /output-style, /release-notes, /feedback, /upgrade, /stickers, /mobile,
|
||||
/desktop, /install-github-app, /install-slack-app, /privacy-settings,
|
||||
/extra-usage, /passes, /rate-limit-options, /chrome, /reload-plugins.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
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)),
|
||||
)
|
||||
|
||||
|
||||
class ExternalSlashCommandsTest(unittest.TestCase):
|
||||
"""Each test runs with CLAUDE_CODE_NO_BROWSER=1 so no browser opens."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
os.environ['CLAUDE_CODE_NO_BROWSER'] = '1'
|
||||
|
||||
def tearDown(self) -> None:
|
||||
os.environ.pop('CLAUDE_CODE_NO_BROWSER', None)
|
||||
|
||||
def _run(self, cmd: str) -> str:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
agent = _make_agent(tmp)
|
||||
return agent.run(cmd).final_output
|
||||
|
||||
def test_output_style_is_deprecated(self) -> None:
|
||||
out = self._run('/output-style')
|
||||
self.assertIn('deprecated', out.lower())
|
||||
self.assertIn('/config', out)
|
||||
|
||||
def test_release_notes_falls_back_to_link(self) -> None:
|
||||
out = self._run('/release-notes')
|
||||
self.assertIn('CHANGELOG.md', out)
|
||||
self.assertIn('https://github.com/anthropics/claude-code', out)
|
||||
|
||||
def test_release_notes_reads_local_changelog(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
(Path(tmp) / 'CHANGELOG.md').write_text(
|
||||
'# Changelog\n\n## 1.2.3\n- did a thing\n\n## 1.2.2\n- old\n',
|
||||
encoding='utf-8',
|
||||
)
|
||||
agent = _make_agent(tmp)
|
||||
out = agent.run('/release-notes').final_output
|
||||
self.assertIn('1.2.3', out)
|
||||
self.assertIn('did a thing', out)
|
||||
self.assertNotIn('1.2.2', out)
|
||||
|
||||
def test_feedback_returns_link(self) -> None:
|
||||
out = self._run('/feedback')
|
||||
self.assertIn('https://github.com/anthropics/claude-code/issues', out)
|
||||
|
||||
def test_bug_aliases_to_feedback(self) -> None:
|
||||
out = self._run('/bug')
|
||||
self.assertIn('https://github.com/anthropics/claude-code/issues', out)
|
||||
|
||||
def test_feedback_includes_user_note(self) -> None:
|
||||
out = self._run('/feedback the wrap selector keeps eating my prompt')
|
||||
self.assertIn('Draft note', out)
|
||||
self.assertIn('wrap selector', out)
|
||||
|
||||
def test_upgrade_returns_link(self) -> None:
|
||||
out = self._run('/upgrade')
|
||||
self.assertIn('https://claude.ai/upgrade/max', out)
|
||||
|
||||
def test_stickers_returns_link(self) -> None:
|
||||
out = self._run('/stickers')
|
||||
self.assertIn('stickermule.com/claudecode', out)
|
||||
|
||||
def test_mobile_lists_both_stores(self) -> None:
|
||||
out = self._run('/mobile')
|
||||
self.assertIn('apps.apple.com', out)
|
||||
self.assertIn('play.google.com', out)
|
||||
|
||||
def test_ios_alias(self) -> None:
|
||||
out = self._run('/ios')
|
||||
self.assertIn('apps.apple.com', out)
|
||||
|
||||
def test_android_alias(self) -> None:
|
||||
out = self._run('/android')
|
||||
self.assertIn('play.google.com', out)
|
||||
|
||||
def test_desktop_returns_link(self) -> None:
|
||||
out = self._run('/desktop')
|
||||
self.assertIn('claude.ai/download', out)
|
||||
|
||||
def test_app_aliases_to_desktop(self) -> None:
|
||||
out = self._run('/app')
|
||||
self.assertIn('claude.ai/download', out)
|
||||
|
||||
def test_install_github_app(self) -> None:
|
||||
out = self._run('/install-github-app')
|
||||
self.assertIn('github.com/apps/claude', out)
|
||||
|
||||
def test_install_slack_app(self) -> None:
|
||||
out = self._run('/install-slack-app')
|
||||
self.assertIn('slack.com/marketplace/A08SF47R6P4-claude', out)
|
||||
|
||||
def test_privacy_settings(self) -> None:
|
||||
out = self._run('/privacy-settings')
|
||||
self.assertIn('claude.ai/settings/data-privacy-controls', out)
|
||||
|
||||
def test_extra_usage_points_to_upgrade(self) -> None:
|
||||
out = self._run('/extra-usage')
|
||||
self.assertIn('claude.ai/upgrade/max', out)
|
||||
self.assertIn('/login', out)
|
||||
|
||||
def test_passes_mentions_claude_ai(self) -> None:
|
||||
out = self._run('/passes')
|
||||
self.assertIn('claude.ai', out.lower())
|
||||
self.assertIn('passes', out.lower())
|
||||
|
||||
def test_rate_limit_options_lists_actions(self) -> None:
|
||||
out = self._run('/rate-limit-options')
|
||||
self.assertIn('/upgrade', out)
|
||||
self.assertIn('/extra-usage', out)
|
||||
self.assertIn('/login', out)
|
||||
|
||||
def test_chrome_returns_link(self) -> None:
|
||||
out = self._run('/chrome')
|
||||
self.assertIn('claude.ai/chrome', out)
|
||||
|
||||
def test_reload_plugins_reports_counts(self) -> None:
|
||||
out = self._run('/reload-plugins')
|
||||
self.assertIn('Reloaded plugins', out)
|
||||
self.assertIn('plugin(s)', out)
|
||||
self.assertIn('tool(s)', out)
|
||||
self.assertIn('hook(s)', out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user