feat: improve docs and logging for Multi-Org actions in CLI (#2980)

* docs: add organization management in our CLI docs

* feat: improve user feedback when user is not authenticated

* feat: improve logging about current organization while publishing/install a Tool

* feat: improve logging when Agent repository is not found during fetch

* fix linter offences

* test: fix auth token error
This commit is contained in:
Lucas Gomide
2025-06-09 13:21:12 -03:00
committed by GitHub
parent 8a37b535ed
commit db3c8a49bd
7 changed files with 226 additions and 34 deletions

View File

@@ -200,6 +200,37 @@ Deploy the crew or flow to [CrewAI Enterprise](https://app.crewai.com).
``` ```
- Reads your local project configuration. - Reads your local project configuration.
- Prompts you to confirm the environment variables (like `OPENAI_API_KEY`, `SERPER_API_KEY`) found locally. These will be securely stored with the deployment on the Enterprise platform. Ensure your sensitive keys are correctly configured locally (e.g., in a `.env` file) before running this. - Prompts you to confirm the environment variables (like `OPENAI_API_KEY`, `SERPER_API_KEY`) found locally. These will be securely stored with the deployment on the Enterprise platform. Ensure your sensitive keys are correctly configured locally (e.g., in a `.env` file) before running this.
### 11. Organization Management
Manage your CrewAI Enterprise organizations.
```shell Terminal
crewai org [COMMAND] [OPTIONS]
```
#### Commands:
- `list`: List all organizations you belong to
```shell Terminal
crewai org list
```
- `current`: Display your currently active organization
```shell Terminal
crewai org current
```
- `switch`: Switch to a specific organization
```shell Terminal
crewai org switch <organization_id>
```
<Note>
You must be authenticated to CrewAI Enterprise to use these organization management commands.
</Note>
- **Create a deployment** (continued):
- Links the deployment to the corresponding remote GitHub repository (it usually detects this automatically). - Links the deployment to the corresponding remote GitHub repository (it usually detects this automatically).
- **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise. - **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise.

View File

@@ -1,6 +1,7 @@
from rich.console import Console from rich.console import Console
from rich.table import Table from rich.table import Table
from requests import HTTPError
from crewai.cli.command import BaseCommand, PlusAPIMixin from crewai.cli.command import BaseCommand, PlusAPIMixin
from crewai.cli.config import Settings from crewai.cli.config import Settings
@@ -16,7 +17,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
response = self.plus_api_client.get_organizations() response = self.plus_api_client.get_organizations()
response.raise_for_status() response.raise_for_status()
orgs = response.json() orgs = response.json()
if not orgs: if not orgs:
console.print("You don't belong to any organizations yet.", style="yellow") console.print("You don't belong to any organizations yet.", style="yellow")
return return
@@ -26,8 +27,14 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
table.add_column("ID", style="green") table.add_column("ID", style="green")
for org in orgs: for org in orgs:
table.add_row(org["name"], org["uuid"]) table.add_row(org["name"], org["uuid"])
console.print(table) console.print(table)
except HTTPError as e:
if e.response.status_code == 401:
console.print("You are not logged in to any organization. Use 'crewai login' to login.", style="bold red")
return
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
raise SystemExit(1)
except Exception as e: except Exception as e:
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red") console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
raise SystemExit(1) raise SystemExit(1)
@@ -37,18 +44,24 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
response = self.plus_api_client.get_organizations() response = self.plus_api_client.get_organizations()
response.raise_for_status() response.raise_for_status()
orgs = response.json() orgs = response.json()
org = next((o for o in orgs if o["uuid"] == org_id), None) org = next((o for o in orgs if o["uuid"] == org_id), None)
if not org: if not org:
console.print(f"Organization with id '{org_id}' not found.", style="bold red") console.print(f"Organization with id '{org_id}' not found.", style="bold red")
return return
settings = Settings() settings = Settings()
settings.org_name = org["name"] settings.org_name = org["name"]
settings.org_uuid = org["uuid"] settings.org_uuid = org["uuid"]
settings.dump() settings.dump()
console.print(f"Successfully switched to {org['name']} ({org['uuid']})", style="bold green") console.print(f"Successfully switched to {org['name']} ({org['uuid']})", style="bold green")
except HTTPError as e:
if e.response.status_code == 401:
console.print("You are not logged in to any organization. Use 'crewai login' to login.", style="bold red")
return
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
raise SystemExit(1)
except Exception as e: except Exception as e:
console.print(f"Failed to switch organization: {str(e)}", style="bold red") console.print(f"Failed to switch organization: {str(e)}", style="bold red")
raise SystemExit(1) raise SystemExit(1)

View File

@@ -91,6 +91,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
console.print( console.print(
f"[green]Found these tools to publish: {', '.join([e['name'] for e in available_exports])}[/green]" f"[green]Found these tools to publish: {', '.join([e['name'] for e in available_exports])}[/green]"
) )
self._print_current_organization()
with tempfile.TemporaryDirectory() as temp_build_dir: with tempfile.TemporaryDirectory() as temp_build_dir:
subprocess.run( subprocess.run(
@@ -136,6 +137,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
) )
def install(self, handle: str): def install(self, handle: str):
self._print_current_organization()
get_response = self.plus_api_client.get_tool(handle) get_response = self.plus_api_client.get_tool(handle)
if get_response.status_code == 404: if get_response.status_code == 404:
@@ -182,7 +184,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
settings.dump() settings.dump()
console.print( console.print(
"Successfully authenticated to the tool repository.", style="bold green" f"Successfully authenticated to the tool repository as {settings.org_name} ({settings.org_uuid}).", style="bold green"
) )
def _add_package(self, tool_details: dict[str, Any]): def _add_package(self, tool_details: dict[str, Any]):
@@ -240,3 +242,10 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
) )
return env return env
def _print_current_organization(self):
settings = Settings()
if settings.org_uuid:
console.print(f"Current organization: {settings.org_name} ({settings.org_uuid})", style="bold blue")
else:
console.print("No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.", style="yellow")

View File

@@ -20,7 +20,10 @@ from crewai.utilities.errors import AgentRepositoryError
from crewai.utilities.exceptions.context_window_exceeding_exception import ( from crewai.utilities.exceptions.context_window_exceeding_exception import (
LLMContextLengthExceededException, LLMContextLengthExceededException,
) )
from rich.console import Console
from crewai.cli.config import Settings
console = Console()
def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]: def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]:
"""Parse tools to be used for the task.""" """Parse tools to be used for the task."""
@@ -435,6 +438,13 @@ def show_agent_logs(
) )
def _print_current_organization():
settings = Settings()
if settings.org_uuid:
console.print(f"Fetching agent from organization: {settings.org_name} ({settings.org_uuid})", style="bold blue")
else:
console.print("No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.", style="yellow")
def load_agent_from_repository(from_repository: str) -> Dict[str, Any]: def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
attributes: Dict[str, Any] = {} attributes: Dict[str, Any] = {}
if from_repository: if from_repository:
@@ -444,15 +454,18 @@ def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
from crewai.cli.plus_api import PlusAPI from crewai.cli.plus_api import PlusAPI
client = PlusAPI(api_key=get_auth_token()) client = PlusAPI(api_key=get_auth_token())
_print_current_organization()
response = client.get_agent(from_repository) response = client.get_agent(from_repository)
if response.status_code == 404: if response.status_code == 404:
raise AgentRepositoryError( raise AgentRepositoryError(
f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization" f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization."
f"\nIf you are using the wrong organization, switch to the correct one using `crewai org switch <org_id>` command.",
) )
if response.status_code != 200: if response.status_code != 200:
raise AgentRepositoryError( raise AgentRepositoryError(
f"Agent {from_repository} could not be loaded: {response.text}" f"Agent {from_repository} could not be loaded: {response.text}"
f"\nIf you are using the wrong organization, switch to the correct one using `crewai org switch <org_id>` command.",
) )
agent = response.json() agent = response.json()

View File

@@ -2126,3 +2126,60 @@ def test_agent_from_repository_agent_not_found(mock_get_agent, mock_get_auth_tok
match="Agent test_agent does not exist, make sure the name is correct or the agent is available on your organization", match="Agent test_agent does not exist, make sure the name is correct or the agent is available on your organization",
): ):
Agent(from_repository="test_agent") Agent(from_repository="test_agent")
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
@patch("crewai.utilities.agent_utils.Settings")
@patch("crewai.utilities.agent_utils.console")
def test_agent_from_repository_displays_org_info(mock_console, mock_settings, mock_get_agent, mock_get_auth_token):
mock_settings_instance = MagicMock()
mock_settings_instance.org_uuid = "test-org-uuid"
mock_settings_instance.org_name = "Test Organization"
mock_settings.return_value = mock_settings_instance
mock_get_response = MagicMock()
mock_get_response.status_code = 200
mock_get_response.json.return_value = {
"role": "test role",
"goal": "test goal",
"backstory": "test backstory",
"tools": []
}
mock_get_agent.return_value = mock_get_response
agent = Agent(from_repository="test_agent")
mock_console.print.assert_any_call(
"Fetching agent from organization: Test Organization (test-org-uuid)",
style="bold blue"
)
assert agent.role == "test role"
assert agent.goal == "test goal"
assert agent.backstory == "test backstory"
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
@patch("crewai.utilities.agent_utils.Settings")
@patch("crewai.utilities.agent_utils.console")
def test_agent_from_repository_without_org_set(mock_console, mock_settings, mock_get_agent, mock_get_auth_token):
mock_settings_instance = MagicMock()
mock_settings_instance.org_uuid = None
mock_settings_instance.org_name = None
mock_settings.return_value = mock_settings_instance
mock_get_response = MagicMock()
mock_get_response.status_code = 401
mock_get_response.text = "Unauthorized access"
mock_get_agent.return_value = mock_get_response
with pytest.raises(
AgentRepositoryError,
match="Agent test_agent could not be loaded: Unauthorized access"
):
Agent(from_repository="test_agent")
mock_console.print.assert_any_call(
"No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.",
style="yellow"
)

View File

@@ -33,9 +33,9 @@ def mock_settings():
def test_org_list_command(mock_org_command_class, runner): def test_org_list_command(mock_org_command_class, runner):
mock_org_instance = MagicMock() mock_org_instance = MagicMock()
mock_org_command_class.return_value = mock_org_instance mock_org_command_class.return_value = mock_org_instance
result = runner.invoke(list) result = runner.invoke(list)
assert result.exit_code == 0 assert result.exit_code == 0
mock_org_command_class.assert_called_once() mock_org_command_class.assert_called_once()
mock_org_instance.list.assert_called_once() mock_org_instance.list.assert_called_once()
@@ -45,9 +45,9 @@ def test_org_list_command(mock_org_command_class, runner):
def test_org_switch_command(mock_org_command_class, runner): def test_org_switch_command(mock_org_command_class, runner):
mock_org_instance = MagicMock() mock_org_instance = MagicMock()
mock_org_command_class.return_value = mock_org_instance mock_org_command_class.return_value = mock_org_instance
result = runner.invoke(switch, ['test-id']) result = runner.invoke(switch, ['test-id'])
assert result.exit_code == 0 assert result.exit_code == 0
mock_org_command_class.assert_called_once() mock_org_command_class.assert_called_once()
mock_org_instance.switch.assert_called_once_with('test-id') mock_org_instance.switch.assert_called_once_with('test-id')
@@ -57,9 +57,9 @@ def test_org_switch_command(mock_org_command_class, runner):
def test_org_current_command(mock_org_command_class, runner): def test_org_current_command(mock_org_command_class, runner):
mock_org_instance = MagicMock() mock_org_instance = MagicMock()
mock_org_command_class.return_value = mock_org_instance mock_org_command_class.return_value = mock_org_instance
result = runner.invoke(current) result = runner.invoke(current)
assert result.exit_code == 0 assert result.exit_code == 0
mock_org_command_class.assert_called_once() mock_org_command_class.assert_called_once()
mock_org_instance.current.assert_called_once() mock_org_instance.current.assert_called_once()
@@ -70,7 +70,7 @@ class TestOrganizationCommand(unittest.TestCase):
with patch.object(OrganizationCommand, '__init__', return_value=None): with patch.object(OrganizationCommand, '__init__', return_value=None):
self.org_command = OrganizationCommand() self.org_command = OrganizationCommand()
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
@patch('crewai.cli.organization.main.console') @patch('crewai.cli.organization.main.console')
@patch('crewai.cli.organization.main.Table') @patch('crewai.cli.organization.main.Table')
def test_list_organizations_success(self, mock_table, mock_console): def test_list_organizations_success(self, mock_table, mock_console):
@@ -82,11 +82,11 @@ class TestOrganizationCommand(unittest.TestCase):
] ]
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.return_value = mock_response self.org_command.plus_api_client.get_organizations.return_value = mock_response
mock_console.print = MagicMock() mock_console.print = MagicMock()
self.org_command.list() self.org_command.list()
self.org_command.plus_api_client.get_organizations.assert_called_once() self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_table.assert_called_once_with(title="Your Organizations") mock_table.assert_called_once_with(title="Your Organizations")
mock_table.return_value.add_column.assert_has_calls([ mock_table.return_value.add_column.assert_has_calls([
@@ -105,12 +105,12 @@ class TestOrganizationCommand(unittest.TestCase):
mock_response.json.return_value = [] mock_response.json.return_value = []
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.return_value = mock_response self.org_command.plus_api_client.get_organizations.return_value = mock_response
self.org_command.list() self.org_command.list()
self.org_command.plus_api_client.get_organizations.assert_called_once() self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_console.print.assert_called_once_with( mock_console.print.assert_called_once_with(
"You don't belong to any organizations yet.", "You don't belong to any organizations yet.",
style="yellow" style="yellow"
) )
@@ -118,14 +118,14 @@ class TestOrganizationCommand(unittest.TestCase):
def test_list_organizations_api_error(self, mock_console): def test_list_organizations_api_error(self, mock_console):
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.side_effect = requests.exceptions.RequestException("API Error") self.org_command.plus_api_client.get_organizations.side_effect = requests.exceptions.RequestException("API Error")
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
self.org_command.list() self.org_command.list()
self.org_command.plus_api_client.get_organizations.assert_called_once() self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_console.print.assert_called_once_with( mock_console.print.assert_called_once_with(
"Failed to retrieve organization list: API Error", "Failed to retrieve organization list: API Error",
style="bold red" style="bold red"
) )
@@ -140,12 +140,12 @@ class TestOrganizationCommand(unittest.TestCase):
] ]
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.return_value = mock_response self.org_command.plus_api_client.get_organizations.return_value = mock_response
mock_settings_instance = MagicMock() mock_settings_instance = MagicMock()
mock_settings_class.return_value = mock_settings_instance mock_settings_class.return_value = mock_settings_instance
self.org_command.switch("test-id") self.org_command.switch("test-id")
self.org_command.plus_api_client.get_organizations.assert_called_once() self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_settings_instance.dump.assert_called_once() mock_settings_instance.dump.assert_called_once()
assert mock_settings_instance.org_name == "Test Org" assert mock_settings_instance.org_name == "Test Org"
@@ -165,9 +165,9 @@ class TestOrganizationCommand(unittest.TestCase):
] ]
self.org_command.plus_api_client = MagicMock() self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.return_value = mock_response self.org_command.plus_api_client.get_organizations.return_value = mock_response
self.org_command.switch("non-existent-id") self.org_command.switch("non-existent-id")
self.org_command.plus_api_client.get_organizations.assert_called_once() self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_console.print.assert_called_once_with( mock_console.print.assert_called_once_with(
"Organization with id 'non-existent-id' not found.", "Organization with id 'non-existent-id' not found.",
@@ -181,9 +181,9 @@ class TestOrganizationCommand(unittest.TestCase):
mock_settings_instance.org_name = "Test Org" mock_settings_instance.org_name = "Test Org"
mock_settings_instance.org_uuid = "test-id" mock_settings_instance.org_uuid = "test-id"
mock_settings_class.return_value = mock_settings_instance mock_settings_class.return_value = mock_settings_instance
self.org_command.current() self.org_command.current()
self.org_command.plus_api_client.get_organizations.assert_not_called() self.org_command.plus_api_client.get_organizations.assert_not_called()
mock_console.print.assert_called_once_with( mock_console.print.assert_called_once_with(
"Currently logged in to organization Test Org (test-id)", "Currently logged in to organization Test Org (test-id)",
@@ -196,11 +196,49 @@ class TestOrganizationCommand(unittest.TestCase):
mock_settings_instance = MagicMock() mock_settings_instance = MagicMock()
mock_settings_instance.org_uuid = None mock_settings_instance.org_uuid = None
mock_settings_class.return_value = mock_settings_instance mock_settings_class.return_value = mock_settings_instance
self.org_command.current() self.org_command.current()
assert mock_console.print.call_count == 3 assert mock_console.print.call_count == 3
mock_console.print.assert_any_call( mock_console.print.assert_any_call(
"You're not currently logged in to any organization.", "You're not currently logged in to any organization.",
style="yellow" style="yellow"
) )
@patch('crewai.cli.organization.main.console')
def test_list_organizations_unauthorized(self, mock_console):
mock_response = MagicMock()
mock_http_error = requests.exceptions.HTTPError(
"401 Client Error: Unauthorized",
response=MagicMock(status_code=401)
)
mock_response.raise_for_status.side_effect = mock_http_error
self.org_command.plus_api_client.get_organizations.return_value = mock_response
self.org_command.list()
self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_console.print.assert_called_once_with(
"You are not logged in to any organization. Use 'crewai login' to login.",
style="bold red"
)
@patch('crewai.cli.organization.main.console')
def test_switch_organization_unauthorized(self, mock_console):
mock_response = MagicMock()
mock_http_error = requests.exceptions.HTTPError(
"401 Client Error: Unauthorized",
response=MagicMock(status_code=401)
)
mock_response.raise_for_status.side_effect = mock_http_error
self.org_command.plus_api_client.get_organizations.return_value = mock_response
self.org_command.switch("test-id")
self.org_command.plus_api_client.get_organizations.assert_called_once()
mock_console.print.assert_called_once_with(
"You are not logged in to any organization. Use 'crewai login' to login.",
style="bold red"
)

View File

@@ -56,7 +56,8 @@ def test_create_success(mock_subprocess, capsys, tool_command):
@patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.subprocess.run")
@patch("crewai.cli.plus_api.PlusAPI.get_tool") @patch("crewai.cli.plus_api.PlusAPI.get_tool")
def test_install_success(mock_get, mock_subprocess_run, capsys, tool_command): @patch("crewai.cli.tools.main.ToolCommand._print_current_organization")
def test_install_success(mock_print_org, mock_get, mock_subprocess_run, capsys, tool_command):
mock_get_response = MagicMock() mock_get_response = MagicMock()
mock_get_response.status_code = 200 mock_get_response.status_code = 200
mock_get_response.json.return_value = { mock_get_response.json.return_value = {
@@ -85,6 +86,9 @@ def test_install_success(mock_get, mock_subprocess_run, capsys, tool_command):
env=unittest.mock.ANY, env=unittest.mock.ANY,
) )
# Verify _print_current_organization was called
mock_print_org.assert_called_once()
@patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.subprocess.run")
@patch("crewai.cli.plus_api.PlusAPI.get_tool") @patch("crewai.cli.plus_api.PlusAPI.get_tool")
def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command): def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command):
@@ -166,7 +170,9 @@ def test_publish_when_not_in_sync(mock_is_synced, capsys, tool_command):
@patch("crewai.cli.plus_api.PlusAPI.publish_tool") @patch("crewai.cli.plus_api.PlusAPI.publish_tool")
@patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False) @patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False)
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}]) @patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
@patch("crewai.cli.tools.main.ToolCommand._print_current_organization")
def test_publish_when_not_in_sync_and_force( def test_publish_when_not_in_sync_and_force(
mock_print_org,
mock_available_exports, mock_available_exports,
mock_is_synced, mock_is_synced,
mock_publish, mock_publish,
@@ -202,6 +208,7 @@ def test_publish_when_not_in_sync_and_force(
encoded_file=unittest.mock.ANY, encoded_file=unittest.mock.ANY,
available_exports=[{"name": "SampleTool"}], available_exports=[{"name": "SampleTool"}],
) )
mock_print_org.assert_called_once()
@patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool") @patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool")
@@ -329,3 +336,27 @@ def test_publish_api_error(
assert "Request to Enterprise API failed" in output assert "Request to Enterprise API failed" in output
mock_publish.assert_called_once() mock_publish.assert_called_once()
@patch("crewai.cli.tools.main.Settings")
def test_print_current_organization_with_org(mock_settings, capsys, tool_command):
mock_settings_instance = MagicMock()
mock_settings_instance.org_uuid = "test-org-uuid"
mock_settings_instance.org_name = "Test Organization"
mock_settings.return_value = mock_settings_instance
tool_command._print_current_organization()
output = capsys.readouterr().out
assert "Current organization: Test Organization (test-org-uuid)" in output
@patch("crewai.cli.tools.main.Settings")
def test_print_current_organization_without_org(mock_settings, capsys, tool_command):
mock_settings_instance = MagicMock()
mock_settings_instance.org_uuid = None
mock_settings_instance.org_name = None
mock_settings.return_value = mock_settings_instance
tool_command._print_current_organization()
output = capsys.readouterr().out
assert "No organization currently set" in output
assert "org switch <org_id>" in output