We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
bug: identical project name prevents selection of default project
```
❯ meltano-cloud project use --name taylor-test2
2023-08-21 16:34:58 [debug    ] Making Cloud CLI HTTP request  method=GET url=https://internal.api.meltano.cloud/projects/v1/01GV0Z1VW5TSRF8BYX1J2V3WNT
Error: Multiple Meltano Cloud projects have the same name. Please specify the project using the `--id` option with its internal ID, shown below. Note that these IDs may change at any time. To avoid this issue, please use unique project names.
01GGWBJ538JBBW4TV91E4ST1PF: Meltano Squared ('https://github.com/meltano/squared.git')
01GVR8N319XJPPG9C8184GCKQB: private-meltano-demo-in-a-box ('https://github.com/meltano/meltano-demo-in-a-box.git')
01H7GHXGAZAXGHZR3WPJ1RS2QE: cody-test-9 ('https://github.com/meltano/test-project-9 --root-path /some/path\n\n.git')
01H7GHY1EH9EMMBRF98N5YJF99: cody-test-9 ('https://github.com/meltano/test-project-9.git')
01H8D07597KR2Y474SSJG3CTT2: taylor-test2 ('https://github.com/meltano/squared.git')
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/meltano/cloud/cli/project.py`
Content:
```
1 """Meltano Cloud `project` command."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import logging
7 import sys
8 import typing as t
9 
10 import click
11 import questionary
12 from ulid import ULID
13 
14 from meltano.cloud.api.client import MeltanoCloudClient
15 from meltano.cloud.cli.base import (
16     LimitedResult,
17     get_paginated,
18     pass_context,
19     print_formatted_list,
20 )
21 from meltano.core.utils import run_async
22 
23 if t.TYPE_CHECKING:
24     from meltano.cloud.api.config import MeltanoCloudConfig
25     from meltano.cloud.api.types import CloudProject
26     from meltano.cloud.cli.base import MeltanoCloudCLIContext
27 
28 DEFAULT_GET_PROJECTS_LIMIT = 125
29 MAX_PAGE_SIZE = 250
30 
31 logger = logging.getLogger()
32 
33 
34 class ULIDType(click.ParamType):
35     """A ULID input type.
36 
37     Examples:
38         01BX5ZZKBKACTAV9WEVGEMMVRY
39         01BX5ZZKBKACTAV9WEVGEMMVS1
40     """
41 
42     name = "ulid"
43 
44     def convert(
45         self,
46         value: str | ULID,
47         param: click.Parameter | None,  # noqa: ARG002
48         ctx: click.Context | None,  # noqa: ARG002
49     ) -> str:
50         """Try converting value to a ULID object."""
51         if isinstance(value, ULID):
52             return str(value)
53 
54         if isinstance(value, str):
55             try:
56                 ULID.from_str(value)
57             except ValueError:
58                 self.fail(f"Invalid ULID value: {value}")
59 
60         return value
61 
62 
63 class ProjectsCloudClient(MeltanoCloudClient):
64     """A Meltano Cloud client with extensions for projects."""
65 
66     async def get_projects(
67         self,
68         *,
69         project_id: str | None = None,
70         project_name: str | None = None,
71         page_size: int | None = None,
72         page_token: str | None = None,
73     ):
74         """Use GET to get Meltano Cloud project projects.
75 
76         Args:
77             project_id: The Meltano Cloud ID for the project.
78             project_name: The name of the project.
79             page_size: The number of items to request per page.
80             page_token: The page token.
81         """
82         async with self.authenticated():
83             return await self._json_request(
84                 "GET",
85                 f"/projects/v1/{self.config.tenant_resource_key}",
86                 params=self.clean_params(
87                     {
88                         "project_id": project_id,
89                         "project_name": project_name,
90                         "page_size": page_size,
91                         "page_token": page_token,
92                     },
93                 ),
94             )
95 
96 
97 @click.group("project")
98 def project_group() -> None:
99     """Interact with Meltano Cloud projects."""
100 
101 
102 def _safe_get_internal_project_id(config: MeltanoCloudConfig) -> str | None:
103     """Get the internal project ID, or `None` if it could not be obtained."""
104     try:
105         return config.internal_project_id
106     except Exception:
107         logger.debug(
108             "Could not get internal project ID from config; using `None` instead.",
109         )
110         return None
111 
112 
113 async def _get_projects(
114     config: MeltanoCloudConfig,
115     *,
116     project_id: str | None = None,
117     project_name: str | None = None,
118     limit: int = DEFAULT_GET_PROJECTS_LIMIT,
119 ) -> LimitedResult[CloudProject]:
120     async with ProjectsCloudClient(config=config) as client:
121         results = await get_paginated(
122             lambda page_size, page_token: client.get_projects(
123                 project_id=project_id,
124                 project_name=project_name,
125                 page_size=page_size,
126                 page_token=page_token,
127             ),
128             limit,
129             MAX_PAGE_SIZE,
130         )
131 
132     results.items = [
133         {
134             **x,
135             "default": x["project_id"] == _safe_get_internal_project_id(config),
136         }
137         for x in results.items
138     ]
139     return results
140 
141 
142 def _format_project(project: dict[str, t.Any]) -> tuple[str, ...]:
143     return (
144         "X" if project["default"] else "",
145         project["project_name"],
146         project["git_repository"],
147     )
148 
149 
150 private_project_attributes = {"tenant_resource_key", "project_id"}
151 
152 
153 def _remove_private_project_attributes(project: CloudProject) -> dict[str, t.Any]:
154     return {k: v for k, v in project.items() if k not in private_project_attributes}
155 
156 
157 @project_group.command("list")
158 @click.option(
159     "--limit",
160     required=False,
161     type=int,
162     default=DEFAULT_GET_PROJECTS_LIMIT,
163     help="The maximum number of projects to display.",
164 )
165 @click.option(
166     "--format",
167     "output_format",
168     required=False,
169     default="terminal",
170     type=click.Choice(("terminal", "markdown", "json")),
171     help="The output format to use.",
172 )
173 @pass_context
174 @run_async
175 async def list_projects(
176     context: MeltanoCloudCLIContext,
177     output_format: str,
178     limit: int,
179 ) -> None:
180     """List Meltano Cloud projects."""
181     results = await _get_projects(config=context.config, limit=limit)
182     stripped_results = LimitedResult(
183         items=[_remove_private_project_attributes(x) for x in results.items],
184         truncated=results.truncated,
185     )
186     print_formatted_list(
187         stripped_results,
188         output_format,
189         _format_project,
190         ("Default", "Name", "Git Repository"),
191         ("center", "left", "left"),
192     )
193 
194 
195 def _check_for_duplicate_project_names(projects: list[CloudProject]) -> None:
196     project_names = [x["project_name"] for x in projects]
197     if len(set(project_names)) != len(project_names):
198         click.secho(
199             "Error: Multiple Meltano Cloud projects have the same name. "
200             "Please specify the project using the `--id` option with its "
201             "internal ID, shown below. Note that these IDs may change at any "
202             "time. To avoid this issue, please use unique project names.",
203             fg="red",
204         )
205         for project in projects:
206             click.echo(
207                 f"{project['project_id']}: {project['project_name']} "
208                 f"({project['git_repository']!r})",
209             )
210         sys.exit(1)
211 
212 
213 class ProjectChoicesQuestionaryOption(click.Option):
214     """Click option that provides an interactive prompt for Cloud Project names."""
215 
216     def prompt_for_value(self, ctx: click.Context) -> t.Any:
217         """Prompt the user to interactively select a Meltano Cloud project by name.
218 
219         Args:
220             ctx: The Click context.
221 
222         Returns:
223             The name of the selected project, or `None` if the project was
224             selected using the `--id` option.
225         """
226         if "project_id" in ctx.params:
227             # The project has been specified by ID - don't prompt for a name
228             return None
229 
230         context: MeltanoCloudCLIContext = ctx.obj
231         context.projects = asyncio.run(_get_projects(context.config)).items
232         _check_for_duplicate_project_names(context.projects)
233         default_project_name = next(
234             (
235                 x
236                 for x in context.projects
237                 if x["project_id"]
238                 == context.config.internal_organization_default["default_project_id"]
239             ),
240             {"project_name": None},
241         )["project_name"]
242         if not context.projects:
243             raise click.ClickException(
244                 "No Meltano Cloud projects available to use. Please create a "
245                 "project before running 'meltano cloud project use'.",
246             )
247         return questionary.select(
248             message="",
249             qmark="Use Meltano Cloud project",
250             choices=[x["project_name"] for x in context.projects],
251             default=default_project_name,
252         ).unsafe_ask()  # Use Click's Ctrl-C handling instead of Questionary's
253 
254 
255 @project_group.command("use")
256 @click.option(
257     "--name",
258     "project_name",
259     cls=ProjectChoicesQuestionaryOption,
260     help=(
261         "The name of a Meltano Cloud project - "
262         "see `meltano cloud project list` for the available options."
263     ),
264     prompt=True,
265 )
266 @click.option(
267     "--id",
268     "project_id",
269     help=(
270         "The internal ID of a Meltano Cloud project - this ID is unstable and "
271         "should only be used if necessary to disambiguate when multiple "
272         "projects share a name."
273     ),
274     default=None,
275     type=ULIDType(),
276 )
277 @pass_context
278 @run_async
279 async def use_project(
280     context: MeltanoCloudCLIContext,
281     project_name: str | None,
282     project_id: str | None,
283 ) -> None:
284     """Set a project as the default to use for Meltano Cloud CLI commands."""
285     if project_id is not None and project_name is not None:
286         raise click.UsageError("The '--name' and '--id' options are mutually exclusive")
287     if project_id is not None:
288         context.config.internal_project_id = project_id
289         click.secho(
290             (
291                 f"Set the project with ID {project_id!r} as the default "
292                 "Meltano Cloud project for future commands"
293             ),
294             fg="green",
295         )
296         return
297 
298     if context.projects is None:  # Interactive config was not used
299         context.projects = (await _get_projects(context.config)).items
300         _check_for_duplicate_project_names(context.projects)
301         if project_name not in {x["project_name"] for x in context.projects}:
302             raise click.ClickException(
303                 f"Unable to use project named {project_name!r} - no available "
304                 "project matches name.",
305             )
306     context.config.internal_project_id = next(
307         x for x in context.projects if x["project_name"] == project_name
308     )["project_id"]
309     click.secho(
310         (
311             f"Set {project_name!r} as the default Meltano Cloud project for "
312             "future commands"
313         ),
314         fg="green",
315     )
316
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/meltano/cloud/cli/project.py b/src/meltano/cloud/cli/project.py
--- a/src/meltano/cloud/cli/project.py
+++ b/src/meltano/cloud/cli/project.py
@@ -192,21 +192,42 @@
     )
 
 
+def _print_projects(projects: list[CloudProject]) -> None:
+    for project in projects:
+        click.echo(
+            f"{project['project_id']}: {project['project_name']} "
+            f"({project['git_repository']!r})",
+        )
+
+
 def _check_for_duplicate_project_names(projects: list[CloudProject]) -> None:
     project_names = [x["project_name"] for x in projects]
     if len(set(project_names)) != len(project_names):
         click.secho(
-            "Error: Multiple Meltano Cloud projects have the same name. "
+            "Error: Multiple Meltano Cloud projects have the same name. If you are "
+            "trying to use a project with an unambiguous name, please select it with "
+            "the `--name` option. Otherwise, please specify the project using the "
+            "`--id` option with its internal ID, shown below. Note that these IDs may "
+            "change at any time. To avoid this issue, please use unique project names.",
+            fg="red",
+        )
+        _print_projects(projects)
+        sys.exit(1)
+
+
+def _check_for_project_name_conflict(
+    projects: list[CloudProject],
+    project_name: str,
+) -> None:
+    if [x["project_name"] for x in projects].count(project_name) > 1:
+        click.secho(
+            "Error: Multiple Meltano Cloud projects have the specified name. "
             "Please specify the project using the `--id` option with its "
             "internal ID, shown below. Note that these IDs may change at any "
             "time. To avoid this issue, please use unique project names.",
             fg="red",
         )
-        for project in projects:
-            click.echo(
-                f"{project['project_id']}: {project['project_name']} "
-                f"({project['git_repository']!r})",
-            )
+        _print_projects(projects)
         sys.exit(1)
 
 
@@ -297,7 +318,7 @@
 
     if context.projects is None:  # Interactive config was not used
         context.projects = (await _get_projects(context.config)).items
-        _check_for_duplicate_project_names(context.projects)
+        _check_for_project_name_conflict(context.projects, t.cast(str, project_name))
         if project_name not in {x["project_name"] for x in context.projects}:
             raise click.ClickException(
                 f"Unable to use project named {project_name!r} - no available "

{"golden_diff": "diff --git a/src/meltano/cloud/cli/project.py b/src/meltano/cloud/cli/project.py\n--- a/src/meltano/cloud/cli/project.py\n+++ b/src/meltano/cloud/cli/project.py\n@@ -192,21 +192,42 @@\n     )\n \n \n+def _print_projects(projects: list[CloudProject]) -> None:\n+    for project in projects:\n+        click.echo(\n+            f\"{project['project_id']}: {project['project_name']} \"\n+            f\"({project['git_repository']!r})\",\n+        )\n+\n+\n def _check_for_duplicate_project_names(projects: list[CloudProject]) -> None:\n     project_names = [x[\"project_name\"] for x in projects]\n     if len(set(project_names)) != len(project_names):\n         click.secho(\n-            \"Error: Multiple Meltano Cloud projects have the same name. \"\n+            \"Error: Multiple Meltano Cloud projects have the same name. If you are \"\n+            \"trying to use a project with an unambiguous name, please select it with \"\n+            \"the `--name` option. Otherwise, please specify the project using the \"\n+            \"`--id` option with its internal ID, shown below. Note that these IDs may \"\n+            \"change at any time. To avoid this issue, please use unique project names.\",\n+            fg=\"red\",\n+        )\n+        _print_projects(projects)\n+        sys.exit(1)\n+\n+\n+def _check_for_project_name_conflict(\n+    projects: list[CloudProject],\n+    project_name: str,\n+) -> None:\n+    if [x[\"project_name\"] for x in projects].count(project_name) > 1:\n+        click.secho(\n+            \"Error: Multiple Meltano Cloud projects have the specified name. \"\n             \"Please specify the project using the `--id` option with its \"\n             \"internal ID, shown below. Note that these IDs may change at any \"\n             \"time. To avoid this issue, please use unique project names.\",\n             fg=\"red\",\n         )\n-        for project in projects:\n-            click.echo(\n-                f\"{project['project_id']}: {project['project_name']} \"\n-                f\"({project['git_repository']!r})\",\n-            )\n+        _print_projects(projects)\n         sys.exit(1)\n \n \n@@ -297,7 +318,7 @@\n \n     if context.projects is None:  # Interactive config was not used\n         context.projects = (await _get_projects(context.config)).items\n-        _check_for_duplicate_project_names(context.projects)\n+        _check_for_project_name_conflict(context.projects, t.cast(str, project_name))\n         if project_name not in {x[\"project_name\"] for x in context.projects}:\n             raise click.ClickException(\n                 f\"Unable to use project named {project_name!r} - no available \"\n", "issue": "bug: identical project name prevents selection of default project\n```\r\n\u276f meltano-cloud project use --name taylor-test2\r\n2023-08-21 16:34:58 [debug    ] Making Cloud CLI HTTP request  method=GET url=https://internal.api.meltano.cloud/projects/v1/01GV0Z1VW5TSRF8BYX1J2V3WNT\r\nError: Multiple Meltano Cloud projects have the same name. Please specify the project using the `--id` option with its internal ID, shown below. Note that these IDs may change at any time. To avoid this issue, please use unique project names.\r\n01GGWBJ538JBBW4TV91E4ST1PF: Meltano Squared ('https://github.com/meltano/squared.git')\r\n01GVR8N319XJPPG9C8184GCKQB: private-meltano-demo-in-a-box ('https://github.com/meltano/meltano-demo-in-a-box.git')\r\n01H7GHXGAZAXGHZR3WPJ1RS2QE: cody-test-9 ('https://github.com/meltano/test-project-9 --root-path /some/path\\n\\n.git')\r\n01H7GHY1EH9EMMBRF98N5YJF99: cody-test-9 ('https://github.com/meltano/test-project-9.git')\r\n01H8D07597KR2Y474SSJG3CTT2: taylor-test2 ('https://github.com/meltano/squared.git')\r\n```\n", "before_files": [{"content": "\"\"\"Meltano Cloud `project` command.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport logging\nimport sys\nimport typing as t\n\nimport click\nimport questionary\nfrom ulid import ULID\n\nfrom meltano.cloud.api.client import MeltanoCloudClient\nfrom meltano.cloud.cli.base import (\n    LimitedResult,\n    get_paginated,\n    pass_context,\n    print_formatted_list,\n)\nfrom meltano.core.utils import run_async\n\nif t.TYPE_CHECKING:\n    from meltano.cloud.api.config import MeltanoCloudConfig\n    from meltano.cloud.api.types import CloudProject\n    from meltano.cloud.cli.base import MeltanoCloudCLIContext\n\nDEFAULT_GET_PROJECTS_LIMIT = 125\nMAX_PAGE_SIZE = 250\n\nlogger = logging.getLogger()\n\n\nclass ULIDType(click.ParamType):\n    \"\"\"A ULID input type.\n\n    Examples:\n        01BX5ZZKBKACTAV9WEVGEMMVRY\n        01BX5ZZKBKACTAV9WEVGEMMVS1\n    \"\"\"\n\n    name = \"ulid\"\n\n    def convert(\n        self,\n        value: str | ULID,\n        param: click.Parameter | None,  # noqa: ARG002\n        ctx: click.Context | None,  # noqa: ARG002\n    ) -> str:\n        \"\"\"Try converting value to a ULID object.\"\"\"\n        if isinstance(value, ULID):\n            return str(value)\n\n        if isinstance(value, str):\n            try:\n                ULID.from_str(value)\n            except ValueError:\n                self.fail(f\"Invalid ULID value: {value}\")\n\n        return value\n\n\nclass ProjectsCloudClient(MeltanoCloudClient):\n    \"\"\"A Meltano Cloud client with extensions for projects.\"\"\"\n\n    async def get_projects(\n        self,\n        *,\n        project_id: str | None = None,\n        project_name: str | None = None,\n        page_size: int | None = None,\n        page_token: str | None = None,\n    ):\n        \"\"\"Use GET to get Meltano Cloud project projects.\n\n        Args:\n            project_id: The Meltano Cloud ID for the project.\n            project_name: The name of the project.\n            page_size: The number of items to request per page.\n            page_token: The page token.\n        \"\"\"\n        async with self.authenticated():\n            return await self._json_request(\n                \"GET\",\n                f\"/projects/v1/{self.config.tenant_resource_key}\",\n                params=self.clean_params(\n                    {\n                        \"project_id\": project_id,\n                        \"project_name\": project_name,\n                        \"page_size\": page_size,\n                        \"page_token\": page_token,\n                    },\n                ),\n            )\n\n\n@click.group(\"project\")\ndef project_group() -> None:\n    \"\"\"Interact with Meltano Cloud projects.\"\"\"\n\n\ndef _safe_get_internal_project_id(config: MeltanoCloudConfig) -> str | None:\n    \"\"\"Get the internal project ID, or `None` if it could not be obtained.\"\"\"\n    try:\n        return config.internal_project_id\n    except Exception:\n        logger.debug(\n            \"Could not get internal project ID from config; using `None` instead.\",\n        )\n        return None\n\n\nasync def _get_projects(\n    config: MeltanoCloudConfig,\n    *,\n    project_id: str | None = None,\n    project_name: str | None = None,\n    limit: int = DEFAULT_GET_PROJECTS_LIMIT,\n) -> LimitedResult[CloudProject]:\n    async with ProjectsCloudClient(config=config) as client:\n        results = await get_paginated(\n            lambda page_size, page_token: client.get_projects(\n                project_id=project_id,\n                project_name=project_name,\n                page_size=page_size,\n                page_token=page_token,\n            ),\n            limit,\n            MAX_PAGE_SIZE,\n        )\n\n    results.items = [\n        {\n            **x,\n            \"default\": x[\"project_id\"] == _safe_get_internal_project_id(config),\n        }\n        for x in results.items\n    ]\n    return results\n\n\ndef _format_project(project: dict[str, t.Any]) -> tuple[str, ...]:\n    return (\n        \"X\" if project[\"default\"] else \"\",\n        project[\"project_name\"],\n        project[\"git_repository\"],\n    )\n\n\nprivate_project_attributes = {\"tenant_resource_key\", \"project_id\"}\n\n\ndef _remove_private_project_attributes(project: CloudProject) -> dict[str, t.Any]:\n    return {k: v for k, v in project.items() if k not in private_project_attributes}\n\n\n@project_group.command(\"list\")\n@click.option(\n    \"--limit\",\n    required=False,\n    type=int,\n    default=DEFAULT_GET_PROJECTS_LIMIT,\n    help=\"The maximum number of projects to display.\",\n)\n@click.option(\n    \"--format\",\n    \"output_format\",\n    required=False,\n    default=\"terminal\",\n    type=click.Choice((\"terminal\", \"markdown\", \"json\")),\n    help=\"The output format to use.\",\n)\n@pass_context\n@run_async\nasync def list_projects(\n    context: MeltanoCloudCLIContext,\n    output_format: str,\n    limit: int,\n) -> None:\n    \"\"\"List Meltano Cloud projects.\"\"\"\n    results = await _get_projects(config=context.config, limit=limit)\n    stripped_results = LimitedResult(\n        items=[_remove_private_project_attributes(x) for x in results.items],\n        truncated=results.truncated,\n    )\n    print_formatted_list(\n        stripped_results,\n        output_format,\n        _format_project,\n        (\"Default\", \"Name\", \"Git Repository\"),\n        (\"center\", \"left\", \"left\"),\n    )\n\n\ndef _check_for_duplicate_project_names(projects: list[CloudProject]) -> None:\n    project_names = [x[\"project_name\"] for x in projects]\n    if len(set(project_names)) != len(project_names):\n        click.secho(\n            \"Error: Multiple Meltano Cloud projects have the same name. \"\n            \"Please specify the project using the `--id` option with its \"\n            \"internal ID, shown below. Note that these IDs may change at any \"\n            \"time. To avoid this issue, please use unique project names.\",\n            fg=\"red\",\n        )\n        for project in projects:\n            click.echo(\n                f\"{project['project_id']}: {project['project_name']} \"\n                f\"({project['git_repository']!r})\",\n            )\n        sys.exit(1)\n\n\nclass ProjectChoicesQuestionaryOption(click.Option):\n    \"\"\"Click option that provides an interactive prompt for Cloud Project names.\"\"\"\n\n    def prompt_for_value(self, ctx: click.Context) -> t.Any:\n        \"\"\"Prompt the user to interactively select a Meltano Cloud project by name.\n\n        Args:\n            ctx: The Click context.\n\n        Returns:\n            The name of the selected project, or `None` if the project was\n            selected using the `--id` option.\n        \"\"\"\n        if \"project_id\" in ctx.params:\n            # The project has been specified by ID - don't prompt for a name\n            return None\n\n        context: MeltanoCloudCLIContext = ctx.obj\n        context.projects = asyncio.run(_get_projects(context.config)).items\n        _check_for_duplicate_project_names(context.projects)\n        default_project_name = next(\n            (\n                x\n                for x in context.projects\n                if x[\"project_id\"]\n                == context.config.internal_organization_default[\"default_project_id\"]\n            ),\n            {\"project_name\": None},\n        )[\"project_name\"]\n        if not context.projects:\n            raise click.ClickException(\n                \"No Meltano Cloud projects available to use. Please create a \"\n                \"project before running 'meltano cloud project use'.\",\n            )\n        return questionary.select(\n            message=\"\",\n            qmark=\"Use Meltano Cloud project\",\n            choices=[x[\"project_name\"] for x in context.projects],\n            default=default_project_name,\n        ).unsafe_ask()  # Use Click's Ctrl-C handling instead of Questionary's\n\n\n@project_group.command(\"use\")\n@click.option(\n    \"--name\",\n    \"project_name\",\n    cls=ProjectChoicesQuestionaryOption,\n    help=(\n        \"The name of a Meltano Cloud project - \"\n        \"see `meltano cloud project list` for the available options.\"\n    ),\n    prompt=True,\n)\n@click.option(\n    \"--id\",\n    \"project_id\",\n    help=(\n        \"The internal ID of a Meltano Cloud project - this ID is unstable and \"\n        \"should only be used if necessary to disambiguate when multiple \"\n        \"projects share a name.\"\n    ),\n    default=None,\n    type=ULIDType(),\n)\n@pass_context\n@run_async\nasync def use_project(\n    context: MeltanoCloudCLIContext,\n    project_name: str | None,\n    project_id: str | None,\n) -> None:\n    \"\"\"Set a project as the default to use for Meltano Cloud CLI commands.\"\"\"\n    if project_id is not None and project_name is not None:\n        raise click.UsageError(\"The '--name' and '--id' options are mutually exclusive\")\n    if project_id is not None:\n        context.config.internal_project_id = project_id\n        click.secho(\n            (\n                f\"Set the project with ID {project_id!r} as the default \"\n                \"Meltano Cloud project for future commands\"\n            ),\n            fg=\"green\",\n        )\n        return\n\n    if context.projects is None:  # Interactive config was not used\n        context.projects = (await _get_projects(context.config)).items\n        _check_for_duplicate_project_names(context.projects)\n        if project_name not in {x[\"project_name\"] for x in context.projects}:\n            raise click.ClickException(\n                f\"Unable to use project named {project_name!r} - no available \"\n                \"project matches name.\",\n            )\n    context.config.internal_project_id = next(\n        x for x in context.projects if x[\"project_name\"] == project_name\n    )[\"project_id\"]\n    click.secho(\n        (\n            f\"Set {project_name!r} as the default Meltano Cloud project for \"\n            \"future commands\"\n        ),\n        fg=\"green\",\n    )\n", "path": "src/meltano/cloud/cli/project.py"}], "after_files": [{"content": "\"\"\"Meltano Cloud `project` command.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport logging\nimport sys\nimport typing as t\n\nimport click\nimport questionary\nfrom ulid import ULID\n\nfrom meltano.cloud.api.client import MeltanoCloudClient\nfrom meltano.cloud.cli.base import (\n    LimitedResult,\n    get_paginated,\n    pass_context,\n    print_formatted_list,\n)\nfrom meltano.core.utils import run_async\n\nif t.TYPE_CHECKING:\n    from meltano.cloud.api.config import MeltanoCloudConfig\n    from meltano.cloud.api.types import CloudProject\n    from meltano.cloud.cli.base import MeltanoCloudCLIContext\n\nDEFAULT_GET_PROJECTS_LIMIT = 125\nMAX_PAGE_SIZE = 250\n\nlogger = logging.getLogger()\n\n\nclass ULIDType(click.ParamType):\n    \"\"\"A ULID input type.\n\n    Examples:\n        01BX5ZZKBKACTAV9WEVGEMMVRY\n        01BX5ZZKBKACTAV9WEVGEMMVS1\n    \"\"\"\n\n    name = \"ulid\"\n\n    def convert(\n        self,\n        value: str | ULID,\n        param: click.Parameter | None,  # noqa: ARG002\n        ctx: click.Context | None,  # noqa: ARG002\n    ) -> str:\n        \"\"\"Try converting value to a ULID object.\"\"\"\n        if isinstance(value, ULID):\n            return str(value)\n\n        if isinstance(value, str):\n            try:\n                ULID.from_str(value)\n            except ValueError:\n                self.fail(f\"Invalid ULID value: {value}\")\n\n        return value\n\n\nclass ProjectsCloudClient(MeltanoCloudClient):\n    \"\"\"A Meltano Cloud client with extensions for projects.\"\"\"\n\n    async def get_projects(\n        self,\n        *,\n        project_id: str | None = None,\n        project_name: str | None = None,\n        page_size: int | None = None,\n        page_token: str | None = None,\n    ):\n        \"\"\"Use GET to get Meltano Cloud project projects.\n\n        Args:\n            project_id: The Meltano Cloud ID for the project.\n            project_name: The name of the project.\n            page_size: The number of items to request per page.\n            page_token: The page token.\n        \"\"\"\n        async with self.authenticated():\n            return await self._json_request(\n                \"GET\",\n                f\"/projects/v1/{self.config.tenant_resource_key}\",\n                params=self.clean_params(\n                    {\n                        \"project_id\": project_id,\n                        \"project_name\": project_name,\n                        \"page_size\": page_size,\n                        \"page_token\": page_token,\n                    },\n                ),\n            )\n\n\n@click.group(\"project\")\ndef project_group() -> None:\n    \"\"\"Interact with Meltano Cloud projects.\"\"\"\n\n\ndef _safe_get_internal_project_id(config: MeltanoCloudConfig) -> str | None:\n    \"\"\"Get the internal project ID, or `None` if it could not be obtained.\"\"\"\n    try:\n        return config.internal_project_id\n    except Exception:\n        logger.debug(\n            \"Could not get internal project ID from config; using `None` instead.\",\n        )\n        return None\n\n\nasync def _get_projects(\n    config: MeltanoCloudConfig,\n    *,\n    project_id: str | None = None,\n    project_name: str | None = None,\n    limit: int = DEFAULT_GET_PROJECTS_LIMIT,\n) -> LimitedResult[CloudProject]:\n    async with ProjectsCloudClient(config=config) as client:\n        results = await get_paginated(\n            lambda page_size, page_token: client.get_projects(\n                project_id=project_id,\n                project_name=project_name,\n                page_size=page_size,\n                page_token=page_token,\n            ),\n            limit,\n            MAX_PAGE_SIZE,\n        )\n\n    results.items = [\n        {\n            **x,\n            \"default\": x[\"project_id\"] == _safe_get_internal_project_id(config),\n        }\n        for x in results.items\n    ]\n    return results\n\n\ndef _format_project(project: dict[str, t.Any]) -> tuple[str, ...]:\n    return (\n        \"X\" if project[\"default\"] else \"\",\n        project[\"project_name\"],\n        project[\"git_repository\"],\n    )\n\n\nprivate_project_attributes = {\"tenant_resource_key\", \"project_id\"}\n\n\ndef _remove_private_project_attributes(project: CloudProject) -> dict[str, t.Any]:\n    return {k: v for k, v in project.items() if k not in private_project_attributes}\n\n\n@project_group.command(\"list\")\n@click.option(\n    \"--limit\",\n    required=False,\n    type=int,\n    default=DEFAULT_GET_PROJECTS_LIMIT,\n    help=\"The maximum number of projects to display.\",\n)\n@click.option(\n    \"--format\",\n    \"output_format\",\n    required=False,\n    default=\"terminal\",\n    type=click.Choice((\"terminal\", \"markdown\", \"json\")),\n    help=\"The output format to use.\",\n)\n@pass_context\n@run_async\nasync def list_projects(\n    context: MeltanoCloudCLIContext,\n    output_format: str,\n    limit: int,\n) -> None:\n    \"\"\"List Meltano Cloud projects.\"\"\"\n    results = await _get_projects(config=context.config, limit=limit)\n    stripped_results = LimitedResult(\n        items=[_remove_private_project_attributes(x) for x in results.items],\n        truncated=results.truncated,\n    )\n    print_formatted_list(\n        stripped_results,\n        output_format,\n        _format_project,\n        (\"Default\", \"Name\", \"Git Repository\"),\n        (\"center\", \"left\", \"left\"),\n    )\n\n\ndef _print_projects(projects: list[CloudProject]) -> None:\n    for project in projects:\n        click.echo(\n            f\"{project['project_id']}: {project['project_name']} \"\n            f\"({project['git_repository']!r})\",\n        )\n\n\ndef _check_for_duplicate_project_names(projects: list[CloudProject]) -> None:\n    project_names = [x[\"project_name\"] for x in projects]\n    if len(set(project_names)) != len(project_names):\n        click.secho(\n            \"Error: Multiple Meltano Cloud projects have the same name. If you are \"\n            \"trying to use a project with an unambiguous name, please select it with \"\n            \"the `--name` option. Otherwise, please specify the project using the \"\n            \"`--id` option with its internal ID, shown below. Note that these IDs may \"\n            \"change at any time. To avoid this issue, please use unique project names.\",\n            fg=\"red\",\n        )\n        _print_projects(projects)\n        sys.exit(1)\n\n\ndef _check_for_project_name_conflict(\n    projects: list[CloudProject],\n    project_name: str,\n) -> None:\n    if [x[\"project_name\"] for x in projects].count(project_name) > 1:\n        click.secho(\n            \"Error: Multiple Meltano Cloud projects have the specified name. \"\n            \"Please specify the project using the `--id` option with its \"\n            \"internal ID, shown below. Note that these IDs may change at any \"\n            \"time. To avoid this issue, please use unique project names.\",\n            fg=\"red\",\n        )\n        _print_projects(projects)\n        sys.exit(1)\n\n\nclass ProjectChoicesQuestionaryOption(click.Option):\n    \"\"\"Click option that provides an interactive prompt for Cloud Project names.\"\"\"\n\n    def prompt_for_value(self, ctx: click.Context) -> t.Any:\n        \"\"\"Prompt the user to interactively select a Meltano Cloud project by name.\n\n        Args:\n            ctx: The Click context.\n\n        Returns:\n            The name of the selected project, or `None` if the project was\n            selected using the `--id` option.\n        \"\"\"\n        if \"project_id\" in ctx.params:\n            # The project has been specified by ID - don't prompt for a name\n            return None\n\n        context: MeltanoCloudCLIContext = ctx.obj\n        context.projects = asyncio.run(_get_projects(context.config)).items\n        _check_for_duplicate_project_names(context.projects)\n        default_project_name = next(\n            (\n                x\n                for x in context.projects\n                if x[\"project_id\"]\n                == context.config.internal_organization_default[\"default_project_id\"]\n            ),\n            {\"project_name\": None},\n        )[\"project_name\"]\n        if not context.projects:\n            raise click.ClickException(\n                \"No Meltano Cloud projects available to use. Please create a \"\n                \"project before running 'meltano cloud project use'.\",\n            )\n        return questionary.select(\n            message=\"\",\n            qmark=\"Use Meltano Cloud project\",\n            choices=[x[\"project_name\"] for x in context.projects],\n            default=default_project_name,\n        ).unsafe_ask()  # Use Click's Ctrl-C handling instead of Questionary's\n\n\n@project_group.command(\"use\")\n@click.option(\n    \"--name\",\n    \"project_name\",\n    cls=ProjectChoicesQuestionaryOption,\n    help=(\n        \"The name of a Meltano Cloud project - \"\n        \"see `meltano cloud project list` for the available options.\"\n    ),\n    prompt=True,\n)\n@click.option(\n    \"--id\",\n    \"project_id\",\n    help=(\n        \"The internal ID of a Meltano Cloud project - this ID is unstable and \"\n        \"should only be used if necessary to disambiguate when multiple \"\n        \"projects share a name.\"\n    ),\n    default=None,\n    type=ULIDType(),\n)\n@pass_context\n@run_async\nasync def use_project(\n    context: MeltanoCloudCLIContext,\n    project_name: str | None,\n    project_id: str | None,\n) -> None:\n    \"\"\"Set a project as the default to use for Meltano Cloud CLI commands.\"\"\"\n    if project_id is not None and project_name is not None:\n        raise click.UsageError(\"The '--name' and '--id' options are mutually exclusive\")\n    if project_id is not None:\n        context.config.internal_project_id = project_id\n        click.secho(\n            (\n                f\"Set the project with ID {project_id!r} as the default \"\n                \"Meltano Cloud project for future commands\"\n            ),\n            fg=\"green\",\n        )\n        return\n\n    if context.projects is None:  # Interactive config was not used\n        context.projects = (await _get_projects(context.config)).items\n        _check_for_project_name_conflict(context.projects, t.cast(str, project_name))\n        if project_name not in {x[\"project_name\"] for x in context.projects}:\n            raise click.ClickException(\n                f\"Unable to use project named {project_name!r} - no available \"\n                \"project matches name.\",\n            )\n    context.config.internal_project_id = next(\n        x for x in context.projects if x[\"project_name\"] == project_name\n    )[\"project_id\"]\n    click.secho(\n        (\n            f\"Set {project_name!r} as the default Meltano Cloud project for \"\n            \"future commands\"\n        ),\n        fg=\"green\",\n    )\n", "path": "src/meltano/cloud/cli/project.py"}]}