mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 20:15:56 +00:00
Support for --open_web_dashboard in the CLI
Also extended docs around that topic.
The setting `context_settings={"max_content_width": _MAX_CONTENT_WIDTH}` in the click command makes the CLI --help output more readable and ensures longer links are not broken up into multiple lines. For consistency, I added it to all commands
This commit is contained in:
1 parent
ba62b0ffd3
commit
5897585ef2
4 files changed
+100
-26
No files matched your search
@@ -12,6 +12,9 @@ Serena comes with built-in tools for monitoring and managing the current session
|
||||
By default, it will be accessible at `http://localhost:24282/dashboard/index.html`,
|
||||
but a higher port may be used if the default port is unavailable/multiple instances are running.
|
||||
|
||||
**We recommend always enabling the dashboard**. If you don't want the browser to open automatically,
|
||||
you can disable it while still keeping the dashboard running in the background (see below).
|
||||
|
||||
* the **GUI tool** (disabled by default)
|
||||
|
||||
The GUI tool is a native application window which displays logs.
|
||||
@@ -26,7 +29,8 @@ For the dashboard, this can be disabled if desired (see below).
|
||||
## Disabling Automatic Browser Opening
|
||||
|
||||
If you prefer not to have the dashboard open automatically (e.g., to avoid focus stealing), you can disable it
|
||||
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml`.
|
||||
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml` or by passing `--open-web-dashboard False`
|
||||
to `start-mcp-server` CLI command.
|
||||
|
||||
When automatic opening is disabled, you can still access the dashboard by:
|
||||
* asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
|
||||
|
||||
+83
-24
@@ -36,6 +36,8 @@ from solidlsp.util.subprocess_util import subprocess_kwargs
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_MAX_CONTENT_WIDTH = 100
|
||||
|
||||
|
||||
def find_project_root(root: str | Path | None = None) -> str:
|
||||
"""Find project root by walking up from CWD.
|
||||
@@ -138,7 +140,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
super().__init__(name="serena", help="Serena CLI commands. You can run `<command> --help` for more info on each command.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("start-mcp-server", help="Starts the Serena MCP server.")
|
||||
@click.command("start-mcp-server", help="Starts the Serena MCP server.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.option("--project", "project", type=PROJECT_TYPE, default=None, help="Path or name of project to activate at startup.")
|
||||
@click.option("--project-file", "project", type=PROJECT_TYPE, default=None, help="[DEPRECATED] Use --project instead.")
|
||||
@click.argument("project_file_arg", type=PROJECT_TYPE, required=False, default=None, metavar="")
|
||||
@@ -177,8 +179,29 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
@click.option(
|
||||
"--port", type=int, default=8000, show_default=True, help="Listen port for the MCP server (when using corresponding transport)."
|
||||
)
|
||||
@click.option("--enable-web-dashboard", type=bool, is_flag=False, default=None, help="Override dashboard setting in config.")
|
||||
@click.option("--enable-gui-log-window", type=bool, is_flag=False, default=None, help="Override GUI log window setting in config.")
|
||||
@click.option(
|
||||
"--enable-web-dashboard",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Enable the web dashboard (overriding the setting in Serena's config). "
|
||||
"It is recommended to always enable the dashboard. If you don't want the browser to open on startup, set open-web-dashboard to False. "
|
||||
"For more information, see\nhttps://oraios.github.io/serena/02-usage/060_dashboard.html",
|
||||
)
|
||||
@click.option(
|
||||
"--enable-gui-log-window",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Enable the gui log window (currently only displays logs; overriding the setting in Serena's config).",
|
||||
)
|
||||
@click.option(
|
||||
"--open-web-dashboard",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Open Serena's dashboard in your browser after MCP server startup (overriding the setting in Serena's config).",
|
||||
)
|
||||
@click.option(
|
||||
"--log-level",
|
||||
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
|
||||
@@ -204,6 +227,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
host: str,
|
||||
port: int,
|
||||
enable_web_dashboard: bool | None,
|
||||
open_web_dashboard: bool | None,
|
||||
enable_gui_log_window: bool | None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None,
|
||||
trace_lsp_communication: bool | None,
|
||||
@@ -244,6 +268,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
modes=modes,
|
||||
language_backend=LanguageBackend.from_str(language_backend) if language_backend else None,
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
open_web_dashboard=open_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
log_level=log_level,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
@@ -258,7 +283,9 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
server.run(transport=transport)
|
||||
|
||||
@staticmethod
|
||||
@click.command("print-system-prompt", help="Print the system prompt for a project.")
|
||||
@click.command(
|
||||
"print-system-prompt", help="Print the system prompt for a project.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option(
|
||||
"--log-level",
|
||||
@@ -309,7 +336,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
super().__init__(name="mode", help="Manage Serena modes. You can run `mode <command> --help` for more info on each command.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="List available modes.")
|
||||
@click.command("list", help="List available modes.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list() -> None:
|
||||
mode_names = SerenaAgentMode.list_registered_mode_names()
|
||||
max_len_name = max(len(name) for name in mode_names) if mode_names else 20
|
||||
@@ -321,7 +348,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
click.echo(name_descr_string)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new mode or copy an internal one.")
|
||||
@click.command("create", help="Create a new mode or copy an internal one.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.option(
|
||||
"--name",
|
||||
"-n",
|
||||
@@ -350,7 +377,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(dest)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit", help="Edit a custom mode YAML file.")
|
||||
@click.command("edit", help="Edit a custom mode YAML file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("mode_name")
|
||||
def edit(mode_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
@@ -366,7 +393,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete", help="Delete a custom mode file.")
|
||||
@click.command("delete", help="Delete a custom mode file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("mode_name")
|
||||
def delete(mode_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
@@ -386,7 +413,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="List available contexts.")
|
||||
@click.command("list", help="List available contexts.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list() -> None:
|
||||
context_names = SerenaAgentContext.list_registered_context_names()
|
||||
max_len_name = max(len(name) for name in context_names) if context_names else 20
|
||||
@@ -398,7 +425,9 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
click.echo(name_descr_string)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new context or copy an internal one.")
|
||||
@click.command(
|
||||
"create", help="Create a new context or copy an internal one.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.option(
|
||||
"--name",
|
||||
"-n",
|
||||
@@ -427,7 +456,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(dest)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit", help="Edit a custom context YAML file.")
|
||||
@click.command("edit", help="Edit a custom context YAML file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("context_name")
|
||||
def edit(context_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
@@ -443,7 +472,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete", help="Delete a custom context file.")
|
||||
@click.command("delete", help="Delete a custom context file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("context_name")
|
||||
def delete(context_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
@@ -462,7 +491,9 @@ class SerenaConfigCommands(AutoRegisteringGroup):
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"edit", help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found."
|
||||
"edit",
|
||||
help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
def edit() -> None:
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
@@ -512,7 +543,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
return generated_conf
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new Serena project configuration.")
|
||||
@click.command("create", help="Create a new Serena project configuration.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("project_path", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
|
||||
@click.option("--name", type=str, default=None, help="Project name; defaults to directory name if not specified.")
|
||||
@click.option(
|
||||
@@ -538,7 +569,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
raise click.ClickException(str(e))
|
||||
|
||||
@staticmethod
|
||||
@click.command("index", help="Index a project by saving symbols to the LSP cache. Auto-creates project.yml if it doesn't exist.")
|
||||
@click.command(
|
||||
"index",
|
||||
help="Index a project by saving symbols to the LSP cache. Auto-creates project.yml if it doesn't exist.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option("--name", type=str, default=None, help="Project name (only used if auto-creating project.yml).")
|
||||
@click.option(
|
||||
@@ -570,7 +605,9 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ProjectCommands._index_project(project, log_level, timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
@click.command("index-deprecated", help="Deprecated alias for 'serena project index'.")
|
||||
@click.command(
|
||||
"index-deprecated", help="Deprecated alias for 'serena project index'.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="WARNING")
|
||||
@click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file.")
|
||||
@@ -622,7 +659,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ls_mgr.stop_all()
|
||||
|
||||
@staticmethod
|
||||
@click.command("is_ignored_path", help="Check if a path is ignored by the project configuration.")
|
||||
@click.command(
|
||||
"is_ignored_path",
|
||||
help="Check if a path is ignored by the project configuration.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("path", type=click.Path(exists=False, file_okay=True, dir_okay=True))
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
def is_ignored_path(path: str, project: str) -> None:
|
||||
@@ -639,7 +680,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
click.echo(f"Path '{path}' IS {'ignored' if is_ignored else 'IS NOT ignored'} by the project configuration.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("index-file", help="Index a single file by saving its symbols to the LSP cache.")
|
||||
@click.command(
|
||||
"index-file",
|
||||
help="Index a single file by saving its symbols to the LSP cache.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
@click.option("--verbose", "-v", is_flag=True, help="Print detailed information about the indexed symbols.")
|
||||
@@ -672,7 +717,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ls_mgr.stop_all()
|
||||
|
||||
@staticmethod
|
||||
@click.command("health-check", help="Perform a comprehensive health check of the project's tools and language server.")
|
||||
@click.command(
|
||||
"health-check",
|
||||
help="Perform a comprehensive health check of the project's tools and language server.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
def health_check(project: str) -> None:
|
||||
"""
|
||||
@@ -827,6 +876,7 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
@click.command(
|
||||
"list",
|
||||
help="Prints an overview of the tools that are active by default (not just the active ones for your project). For viewing all tools, pass `--all / -a`",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.option("--quiet", "-q", is_flag=True)
|
||||
@click.option("--all", "-a", "include_optional", is_flag=True, help="List all tools, including those not enabled by default.")
|
||||
@@ -849,6 +899,7 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
@click.command(
|
||||
"description",
|
||||
help="Print the description of a tool, optionally with a specific context (the latter may modify the default description).",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("tool_name", type=str)
|
||||
@click.option("--context", type=str, default=None, help="Context name or path to context file.")
|
||||
@@ -879,7 +930,9 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
return os.path.join(templates_dir, prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="Lists yamls that are used for defining prompts.")
|
||||
@click.command(
|
||||
"list", help="Lists yamls that are used for defining prompts.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
def list() -> None:
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||
@@ -890,7 +943,11 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
click.echo(prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create-override", help="Create an override of an internal prompts yaml for customizing Serena's prompts")
|
||||
@click.command(
|
||||
"create-override",
|
||||
help="Create an override of an internal prompts yaml for customizing Serena's prompts",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("prompt_yaml_name")
|
||||
def create_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
@@ -908,7 +965,9 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit-override", help="Edit an existing prompt override file")
|
||||
@click.command(
|
||||
"edit-override", help="Edit an existing prompt override file", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("prompt_yaml_name")
|
||||
def edit_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
@@ -925,7 +984,7 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list-overrides", help="List existing prompt override files")
|
||||
@click.command("list-overrides", help="List existing prompt override files", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list_overrides() -> None:
|
||||
user_templates_dir = SerenaPaths().user_prompt_templates_dir
|
||||
os.makedirs(user_templates_dir, exist_ok=True)
|
||||
@@ -936,7 +995,7 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
click.echo(file_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete-override", help="Delete a prompt override file")
|
||||
@click.command("delete-override", help="Delete a prompt override file", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("prompt_yaml_name")
|
||||
def delete_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -274,6 +274,7 @@ class SerenaMCPFactory:
|
||||
language_backend: LanguageBackend | None = None,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
open_web_dashboard: bool | None = None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
||||
trace_lsp_communication: bool | None = None,
|
||||
tool_timeout: float | None = None,
|
||||
@@ -288,6 +289,8 @@ class SerenaMCPFactory:
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param open_web_dashboard: Whether to open the web dashboard on launch.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level. If not specified, will take the value from the serena configuration.
|
||||
:param trace_lsp_communication: Whether to trace the communication between Serena and the language servers.
|
||||
This is useful for debugging language server issues.
|
||||
@@ -301,6 +304,8 @@ class SerenaMCPFactory:
|
||||
config.web_dashboard = enable_web_dashboard
|
||||
if enable_gui_log_window is not None:
|
||||
config.gui_log_window_enabled = enable_gui_log_window
|
||||
if open_web_dashboard is not None:
|
||||
config.web_dashboard_open_on_launch = open_web_dashboard
|
||||
if log_level is not None:
|
||||
log_level = cast(Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], log_level.upper())
|
||||
config.log_level = logging.getLevelNamesMapping()[log_level]
|
||||
|
||||
@@ -27,6 +27,12 @@ web_dashboard: True
|
||||
# whether to open the Serena web dashboard (which will be accessible through your web browser) that
|
||||
# provides access to Serena's configuration and state as well as the current session logs.
|
||||
# The web dashboard is supported on all platforms.
|
||||
# We strongly recommend to always enable this option, since the dashboard provides important information
|
||||
# about the current state of Serena, including the configuration, the logs and tool usage statistics.
|
||||
# If you don't want the browser window to pop up automatically, set the `web_dashboard_open_on_launch` to False
|
||||
# (either here or through the corresponding flag in the `start-mcp-server` CLI command).
|
||||
# You can then open the dashboard by asking your agent to do so (e.g., by saying "open the dashboard"), Serena provides
|
||||
# a tool for this.
|
||||
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
|
||||
web_dashboard_listen_address: 127.0.0.1
|
||||
@@ -34,7 +40,7 @@ web_dashboard_listen_address: 127.0.0.1
|
||||
|
||||
web_dashboard_open_on_launch: True
|
||||
# whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
|
||||
# is enabled).
|
||||
# is enabled). See also the web_dashboard option.
|
||||
# If set to false, you can still open the dashboard manually by
|
||||
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
|
||||
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
|
||||
|
||||
Reference in new issue
Block a user