UCAgent for cocotb verification - #3
HowToSaveMyLife wants to merge 41 commits into
Conversation
* feat: update formal README.md * docs: improve Formal README * improve some function and workflow in Formal example * add stastic check for formal * add stastic check for formal * improve formal workflow * update static_bug_analysis workflow * update static_bug_analysis workflow * improve formal workflow * add webui for formal workflow
…lysis batch size - Enhance CK tag cross-reference validation in static_bug checker: - Collect all missing CK paths instead of generating separate errors for each - Simplify error message formatting and tag display - Show complete list of missing CK tags to users - Adjust available tags display limit to match number of missing paths - Make static analysis batch size configurable via environment variable (default 1) - Add documentation for new configuration options including pass suggestion model settings, flow control parameters, and test tool configurations
- Add on_stage_complete method to AgentBackendBase and UCAgentLangChainBackend - Implement summary reset functionality in TrimAndSummaryMiddleware - Add system message management to preserve context during reset - Update Chinese config with clearer stage execution steps - Trigger summary reset when stage completes to maintain clean context
- Change null sentinel tag from <BG-STATIC-000-NULL> to <BG-STATIC-NULL> - BG-STATIC-NULL must now be used with <FG-NULL><FC-NULL><CK-NULL> - BG-STATIC-NULL is only used when NO bugs are found in ALL files - BG-STATIC-NULL cannot coexist with other <BG-STATIC-*> tags - Update _check_ck_paths_against_fc_doc to skip NULL path validation - Fix UnityChipBatchCheckerStaticBug.set_workspace to propagate workspace - Add current_batch, progress, task fields to batch checker results - Update test data files and test descriptions - Update documentation in default.yaml and dut_bug_analysis.md
- Add _EventLoopManager singleton to manage a dedicated background event loop - Implement lazy initialization for agent and MemorySaver to ensure they are created in the correct event loop - Fix 'is bound to a different event loop' error when asyncio.Lock objects are used across different event loops - Use run_coroutine_threadsafe for thread-safe coroutine execution
- Add new launch.html page for launching managed tasks with workspace configuration - Add new task.html page for task monitoring and management - Update master.html with navigation buttons to Launch and Task pages - Add launch configuration settings in setting.yaml with default args and env - Fix terminal command building to use cli.py path directly instead of module - Refactor code formatting in api_master.py
- Add stage multi-select functionality with batch operations - Add quick action buttons for stages (HM, Skip, LF, LP) - Add API endpoint for updating stage flags - Improve console panel height constraints and responsiveness - Fix pdb-cmd-bar being overlapped by footer - Add keyboard shortcut Alt+Shift+R to reset UI layout - Enhance stage data with additional metadata (is_current, is_completed, can_edit_flags)
- Use while loop to unwrap all nested wrappers (_ConsoleCapture and PersistentConsoleMirror) - Prevents infinite recursion when wrappers are cross-nested - Fixes RecursionError when starting TUI from web terminal and then exiting
- Fix websocket proxy reconnection issue by properly cleaning up connections - Add correct Origin header for upstream CORS validation - Pass subprotocol from upstream to client during handshake - Increase max message size to 100MB to support large terminal history output - Improve error handling and connection cleanup logic - Fix ClientConnectionResetError and RuntimeError when closing connections
- Add HTTP proxy endpoints: /agent/{agent_id}/cmd, /agent/{agent_id}/terminal, /agent/{id}/web-console
- Add WebSocket proxy endpoints: /agent/{agent_id}/terminal/ws, /agent/{agent_id}/web-console/ws
- Modify /api/agents to return cmd_api_proxy for all agents (not just launched tasks)
- Fix agent.html _taskProxyBase to support both /task and /agent proxy paths
- Add normalization logic for web_console/terminal_api fields in register endpoint
- Support both string and object format for web_console/terminal_api
- Auto-set enabled: true when address is present
- Auto-complete missing fields (host, port, tcp_url)
- Proxy addresses are persistent and survive master restart as they are based on agent ID
- Use cmd_api password for web_console and web_terminal when not explicitly provided - Remove duplicate Basic Auth parameter from WebSocket proxy connections (use only header auth) - Filter out WWW-Authenticate response header to prevent browser auth popup - Fix web_console spec parsing to support space-separated [host:port] [password] format - Ensure consistent authentication flow between HTTP and WebSocket proxies - All web services now share the same password for simplified access
- Add --as-master-persist [path] CLI parameter to use persistent workspace instead of temporary directory when running as master - Default path is /tmp/ucagent_master_persist if no path is provided - Add as_master_persist target in Makefile for convenience
- Generate unique BroadcastChannel name based on current URL path and query params - Same URL still enforces single tab restriction - Different URLs can have independent terminal sessions simultaneously
…I mode - Add get_console_sync_handler() to retrieve current console sync handler - Install console sync handler in CMD API server to capture log output - Fix _stream_chain_records_console to not skip sync when _ConsoleCapture is present - Fix PersistentConsoleMirror to not bypass _ConsoleCapture wrapper, ensuring all output is captured - Console output now shows correctly in agent.html both with and without TUI enabled
- Add _ConsoleCapture detection in _stream_chain_records_console() - Avoid duplicate writes when stdout is already wrapped by _ConsoleCapture - Ensure console output only appears once regardless of --tui flag usage
…s in proxy mode
In proxy mode, the server rewrites API paths in HTML by matching
'/api/' wrapped in double or single quotes. Template literals (backticks)
were not being matched, causing 404 errors for API calls like
/api/changed_files in proxy mode.
Changed three locations to use string concatenation instead of template
literals:
- /api/stage/{stageIndex}/file_current and /api/stage/{stageIndex}/file
- /api/changed_files?count={count}
- /api/cmds?prefix={val}
Add a Master button to the header navigation that is only visible in
proxy mode (when accessing via /task/{id}/cmd or /agent/{id}/cmd paths).
Clicking the button navigates to the root Master Dashboard.
- Add .master-btn CSS styles matching existing button styles
- Add Master link element in header, hidden by default
- Add _initMasterBtn() function to show button in proxy mode
- Call _initMasterBtn() during page initialization
* add a skill for static_bug_analyze stage * optimize implement of skill, and support the hook to setup function in vstage by skill * fix a bug in skill of static_bug_analysis
There was a problem hiding this comment.
Pull request overview
This PR expands UCAgent’s verification workflow support by introducing cocotb-oriented templates/guides, improving skill-system integration (listing, usage tracking, filtering, and stage hooks), and significantly enhancing console/TUI/WebUI runtime/persistence behavior (shared console transcript, web terminal theming, and better interrupt handling).
Changes:
- Add cocotb verification scaffolding (templates + guides) and related launch/config wiring.
- Rework skill tooling and stage integration (ListSkill/RunSkillScript, SetSkillUsage, skill filtering, stage hook loading).
- Improve console/TUI/Web terminal behavior (shared transcript recording, session persistence, runtime status refresh, UI theme shell) and add tests to cover the new behavior.
Reviewed changes
Copilot reviewed 95 out of 102 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
ucagent/verify_agent.py |
Wire in new skill tools and inject formatted skill lists into the system prompt. |
ucagent/util/log.py |
Add optional “console sync” mirroring hook to record console-visible output elsewhere. |
ucagent/util/functions.py |
Skill copy/filter logic updates; add similarity helper used by static-bug checker. |
ucagent/tui/widgets/messages_panel.py |
Add export/restore state for message history persistence across TUI sessions. |
ucagent/tui/widgets/console_input.py |
Pull running-command display from persistent PDB state; allow passing pre-fetched commands. |
ucagent/tui/widgets/console.py |
Persist console history via exported entries; sync console output/commands to vpdb. |
ucagent/tui/utils.py |
Add persistent console mirroring and vpdb-aware capture recording. |
ucagent/tui/mixins/sigint.py |
Log Ctrl+C handling progress to user-visible output. |
ucagent/tui/mixins/console_capture.py |
Improve stdout/stderr capture stacking/restoration; integrate vpdb-aware recording. |
ucagent/tui/app.py |
Restore/save console/messages/command history; runtime refresh loop; better shutdown/cancel flows. |
ucagent/tui/__init__.py |
Ensure session transcript prints to the real underlying stdout stream after exit. |
ucagent/tools/uctool.py |
Move async lock to PrivateAttr and adjust error returns/logging behavior. |
ucagent/stage/vstage.py |
Add skill hook loading on stage init; improve skill-file read detection; stage-complete callback. |
ucagent/stage/vmanager.py |
Replace CheckSkillUsage with SetSkillUsage tool; add formatted skill tips and skill presence checks. |
ucagent/stage/llm_suggestion/langchain_suggestion.py |
Centralize async loop execution via a background event loop; lazy agent init; add recursion limit. |
ucagent/stage/llm_suggestion/base_suggestion.py |
Make get_cfg() safe when _cfg is missing. |
ucagent/setting.yaml |
Add launch defaults and new skill enable/disable/general lists. |
ucagent/server/templates/terminal.html |
Introduce shared shell styling/theming and per-URL BroadcastChannel isolation. |
ucagent/server/static/share/ucagent-shell.js |
Add theme switcher + UI meta footer (version/uptime) plumbing. |
ucagent/server/static/share/ucagent-shell.css |
Styles for the shared shell header/footer and theme toggle. |
ucagent/server/static/share/ucagent-favicon.svg |
New favicon asset. |
ucagent/server/api_terminal.py |
Update web-console command invocation, parsing/binding behavior, UI meta endpoint, eager process start, SIGWINCH propagation. |
ucagent/server/api_cmd.py |
Avoid wrapper nesting, sync log output into capture, add UI meta and stage flag update endpoint, unify running command reporting. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_test_summary.md |
New cocotb workflow summary template. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_static_bug_analysis.md |
New static bug analysis template for cocotb workflow. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_line_coverage_analysis.md |
New line coverage analysis template for cocotb workflow. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_functions_and_checks.md |
New FG/FC/CK main spec template for cocotb workflow. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_function_points_and_test_points.md |
Compatibility placeholder pointing to the new cocotb spec template. |
ucagent/lang/zh/template/cocotb_test/{{DUT}}_bug_analysis.md |
New bug analysis template for cocotb workflow. |
ucagent/lang/zh/template/cocotb_test/tests/{{DUT}}_function_coverage_def.py |
Cocotb-native FG/FC/CK coverage recorder template. |
ucagent/lang/zh/template/cocotb_test/tests/{{DUT}}_api.py |
Cocotb reusable testbench architecture template (Env/Driver/Monitor/Scoreboard/helpers). |
ucagent/lang/zh/template/cocotb_test/tests/test_{{DUT}}.py |
Single-file cocotb test entry template. |
ucagent/lang/zh/template/cocotb_test/tests/Makefile |
Cocotb regression Makefile template. |
ucagent/lang/zh/skills/static-bug-analysis/scripts/recordbug.py |
New skill script for recording static bug entries into the target markdown. |
ucagent/lang/zh/skills/static-bug-analysis/scripts/hooks.py |
Stage hook example for the static-bug-analysis skill. |
ucagent/lang/zh/skills/static-bug-analysis/scripts/__init__.py |
Export setup_vstage for skill hook import. |
ucagent/lang/zh/skills/static-bug-analysis/SKILL.md |
New skill definition for static bug analysis workflow and formatting rules. |
ucagent/lang/zh/skills/markdown-write/SKILL.md |
Removed. |
ucagent/lang/zh/skills/ieee754/SKILL.md |
Removed. |
ucagent/lang/zh/skills/fail-analyze/SKILL.md |
Removed. |
ucagent/lang/zh/doc/Guide_Doc/dut_verification_review_cocotb.md |
New cocotb verification review guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_testbench_correctness_cocotb.md |
New cocotb testbench correctness guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_testbench_architecture_cocotb.md |
New cocotb testbench architecture guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_test_template_cocotb.md |
New cocotb test template guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_test_summary_cocotb.md |
New cocotb test summary guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_test_program_cocotb.md |
New cocotb test programming guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_test_case_cocotb.md |
New cocotb test-case writing guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_static_bug_analysis_cocotb.md |
New cocotb static bug analysis guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_line_coverage_cocotb.md |
New cocotb line coverage guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_function_points_and_test_points_cocotb.md |
New cocotb function/test point guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_function_coverage_def_cocotb.md |
New cocotb functional coverage definition guide. |
ucagent/lang/zh/doc/Guide_Doc/dut_bug_analysis.md |
Update static-bug NULL sentinel rules and formatting examples. |
ucagent/lang/zh/doc/Guide_Doc/dut_api_instruction_cocotb.md |
New cocotb API design guide. |
ucagent/lang/zh/config/default.yaml |
Update prompts/tips and skill-system wording for SetSkillUsage + NULL sentinel rules. |
ucagent/cli.py |
Improve web-console/web-terminal help; add master client-id and persistent master workspace option. |
ucagent/checkers/static_bug.py |
Switch NULL sentinel behavior to BG-STATIC-NULL + validate FG/FC/CK NULL path; improve CK mismatch reporting and batch metadata. |
ucagent/abackend/langchain/middleware/messages.py |
Add summary reset behavior and missing-system-message restoration logic. |
ucagent/abackend/langchain/agent.py |
Reset summary on stage completion; persist system message into middleware. |
ucagent/abackend/cmdline.py |
Make bash command interruption more reliable (process group handling + non-blocking reads). |
ucagent/abackend/base.py |
Add backend on_stage_complete callback hook. |
tests/test_verify_pdb_runtime.py |
Add runtime tests for running-command tracking, cancellation, and shared transcript recording. |
tests/test_static_bug.py |
Update tests for new BG-STATIC-NULL sentinel semantics. |
tests/test_messages_panel.py |
Add persistence tests for message panel + VerifyApp console/messages/history restore/save. |
tests/test_diff_ops.py |
Add FD-leak regression test for diff_ops repo handle cleanup. |
tests/test_cmdline_backend.py |
Add test for interrupting silent long-running subprocesses. |
tests/test_data/static_bug/static_null_mixed.md |
Update NULL sentinel test data. |
tests/test_data/static_bug/static_null.md |
Update NULL sentinel test data format. |
tests/test_data/static_bug/batch_progress_wrong_path.md |
Update batch progress fixture content. |
tests/test_data/static_bug/batch_progress_one.md |
Update batch progress fixture content. |
tests/test_data/static_bug/batch_progress_all_valid.md |
Update batch progress fixture content. |
requirements.txt |
Add uvicorn[standard], remove textual-dev. |
pyproject.toml |
Mirror dependency change (uvicorn[standard], remove textual-dev). |
examples/Formal/scripts/templates/formal_script_template.tcl |
Add suggested engine config and a VCD patcher utility. |
examples/Formal/scripts/templates/checker_template.sv |
Add default clocking/disable and update CK tag formatting. |
examples/Formal/README.md |
Update docker instructions and document WebUI usage. |
examples/Formal/Makefile |
Update formal MCP target flags (master/export-cmd-api). |
examples/Formal/FormalDoc/dut_property_template.md |
Standardize CK tag format and document SVA naming mapping. |
examples/Formal/FormalDoc/dut_bug_report_template.md |
Expand bug report template structure and required fields. |
docs/content/02_usage/03_option.md |
Document environment variables and LLM suggestion toggles. |
README.zh.md |
Update working directory guidance; add master/web UI usage section. |
README.en.md |
Update working directory guidance; add master/web UI usage section. |
Makefile |
Add cocotb init/mcp targets and persistent master target. |
Dockerfile |
Install time; adjust install steps and run ucagent --check during build. |
COCOTB_UCAGENT.md |
Add cocotb integration guide. |
.gitignore |
Ignore output* and normalize .marscode/ entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| proc find_files_recursively {basedir_pattern} {{ | ||
| set filelist {{}} | ||
| # Use a simple, non-recursive glob to get all items in the current directory | ||
| set items [glob -nocomplain [file join $basedir *]] | ||
|
|
||
| foreach item $items {{ | ||
| if {{[file isdirectory $item]}} {{ | ||
| # If it's a directory, recurse into it and add the results | ||
| set subfiles [find_files_recursively $item $pattern] | ||
| foreach subfile $subfiles {{ | ||
| lappend filelist $subfile | ||
| }} | ||
| }} elseif {{[string match $pattern [file tail $item]]}} {{ | ||
| # If it's a file that matches the pattern, add its path to the list |
There was a problem hiding this comment.
The new Tcl helper find_files_recursively has a broken signature/variable usage: it declares {basedir_pattern} but then references $basedir and $pattern, and recursively calls find_files_recursively $item $pattern (two args) even though the proc only accepts one. This will error before patching any VCDs. Define the proc as proc find_files_recursively {basedir pattern} { ... } (or accept a single list argument) and update the internal variable references accordingly.
| def get_loop(self) -> asyncio.AbstractEventLoop: | ||
| if self._loop is None or not self._loop.is_running(): | ||
| with self._lock: | ||
| if self._loop is None or not self._loop.is_running(): | ||
| self._loop = asyncio.new_event_loop() | ||
| self._thread = threading.Thread( | ||
| target=self._run_loop, | ||
| daemon=True, | ||
| name="LLMSuggestionEventLoop" | ||
| ) | ||
| self._thread.start() | ||
| while not self._loop.is_running(): | ||
| pass | ||
| return self._loop |
There was a problem hiding this comment.
_EventLoopManager.get_loop() busy-waits (while not self._loop.is_running(): pass), which can peg a CPU core. Use a threading.Event (set it in _run_loop after set_event_loop) or a small sleep/backoff instead of a tight spin.
| """set the skill usage of curretn stage or return feedback based on skill_usage""" | ||
| current_stage = self.get_current_stage() |
There was a problem hiding this comment.
Typo in docstring: "curretn stage" → "current stage".
| def copytree_incremental(src_dir, dst_dir, skip_existing=True, enable_skill_list=[], disable_skill_list=[]): | ||
| """ | ||
| Incremental copying of directories, supports skipping existing files with the same name and ignoring specified directories. | ||
| Incremental copying of directories with skill-based filtering. | ||
|
|
There was a problem hiding this comment.
copytree_incremental uses mutable default arguments (enable_skill_list=[], disable_skill_list=[]), which can lead to surprising cross-call state sharing if the lists are ever mutated. Use None defaults and normalize inside the function (e.g., enable_skill_list = enable_skill_list or []).
| if self._is_reset_summary: | ||
| self.summary_data = [] | ||
| # Keep only the single most recent human message and discard everything else | ||
| # (including all subsequent AI/tool exchanges). This ensures the context is | ||
| # actually reduced. We must also start with a human message — not an AI/tool | ||
| # message — to satisfy LLM API requirements. | ||
| humam_msg_index = len(llm_input_msgs) - 1 | ||
| while humam_msg_index > 0 and llm_input_msgs[humam_msg_index].type != "human": | ||
| humam_msg_index -= 1 | ||
| # Keep only the human message itself; drop all subsequent AI/tool exchanges. | ||
| tail_msgs = [llm_input_msgs[humam_msg_index]] | ||
| ret["messages"] = [RemoveMessage(id=REMOVE_ALL_MESSAGES)] + role_info + tail_msgs |
There was a problem hiding this comment.
In reset_summary handling, the loop stops at index 0 even if llm_input_msgs[0] is not a human message, and then forces tail_msgs = [llm_input_msgs[humam_msg_index]]. If there is no human message in llm_input_msgs, this can leave an AI/tool message as the first non-system message, which some chat APIs reject. Add an explicit check after the search: if no human message is found, either keep the last message regardless but prepend a synthetic human wrapper, or fall back to keeping the most recent message of an allowed type.
| def find_most_similar_strings(a: Union[str, List[str]], b: List[str]) -> Union[int, List[Tuple[str, int]]]: | ||
| """ | ||
| Find the index of the most similar string in array b. | ||
| If input a is a single string, returns the index directly. | ||
| If input a is a list of strings, returns list of (string, index) tuples. | ||
|
|
||
| Args: | ||
| a: Single string or list of strings to be matched | ||
| b: List of candidate strings to compare against | ||
|
|
||
| Returns: | ||
| The most similar string if a is single string, | ||
| or list of tuples (string_from_a, index_of_most_similar_in_b) if a is list | ||
| """ | ||
| import difflib | ||
| # Handle single string input case | ||
| if isinstance(a, str): | ||
| max_similarity = -1 | ||
| best_index = 0 | ||
| for idx, b_item in enumerate(b): | ||
| similarity = difflib.SequenceMatcher(None, a, b_item).ratio() | ||
| if similarity > max_similarity: | ||
| max_similarity = similarity | ||
| best_index = idx | ||
| return b[best_index] |
There was a problem hiding this comment.
find_most_similar_strings docstring/type hint says it returns an index (or list of (string, index)), but the single-string branch actually returns the matched string (b[best_index]). This mismatch will confuse callers and type-checkers. Either return the index as documented, or update the docstring/return annotation to reflect returning the string (and consider returning the index too if needed).
| def get_skill_root(self): | ||
| return fc.get_abs_path_cwd_ucagent(self.workspace, "skills") | ||
|
|
||
| def is_skill_path(self, file_path): | ||
| skill_root = self.get_skill_root() | ||
| abs_file_path = os.path.abspath(self.workspace + os.path.sep + file_path) | ||
| return abs_file_path.startswith(skill_root) |
There was a problem hiding this comment.
Skill files are now copied into workspace/.ucagent/skills (see copy_skill_files and other call sites), but VerifyStage.get_skill_root() still resolves to workspace/skills. This will make is_skill_path() return false for real skill files, so skill read/usage tracking may stop working. Update get_skill_root() to use .ucagent/skills (prefer os.path.join(workspace, '.ucagent', 'skills') or the existing helper).
| if self.cfg.skill.use_skill: | ||
| formatted_skill_list = list_skills_in_format(_list_skills(self.workspace+"/.ucagent/skills"),self.workspace,self.cfg.skill.general_skill_list) | ||
| skill_prompt = self.cfg.mission.prompt.get_value("skill_system", "").replace("{general_skill_list}", formatted_skill_list) | ||
| system = system.replace("{skill_system}", skill_prompt) | ||
| else: | ||
| system = system.replace("{skill_system}", "") | ||
| warning(f"System prompt: {system}") | ||
| return system |
There was a problem hiding this comment.
get_default_system_prompt() logs the full rendered system prompt at warning level. The system prompt can be large and may include sensitive/internal instructions, so emitting it unconditionally can leak data and bloat console transcripts. Consider removing this log, downgrading to debug, and/or logging only a short/truncated preview behind an explicit verbose flag.
| def _parse_web_console_spec(spec: str) -> tuple[str, int, str]: | ||
| """Parse '--web-console host:port[:password]' value.""" | ||
| if not spec: | ||
| """Parse '--web-console [base_url[:port]] [password]' value.""" | ||
| if not spec or str(spec).strip() == "-1": | ||
| return "localhost", 8000, "" | ||
| parts = spec.split(":", 2) | ||
|
|
||
| addr_part = spec.strip() | ||
| password = "" | ||
|
|
||
| # Support format: [ip[:port]] [password] (space separated) | ||
| if " " in addr_part: | ||
| addr_part, password = addr_part.split(" ", 1) | ||
| password = password.strip() | ||
| addr_part = addr_part.strip() | ||
|
|
||
| # Now parse the address part | ||
| parts = addr_part.split(":", 1) | ||
| if len(parts) < 2: | ||
| raise ValueError( | ||
| f"Invalid --web-console value '{spec}'. Expected format: base_url:port[:password]" | ||
| f"Invalid --web-console value '{spec}'. Expected format: [base_url[:port]] [password]" | ||
| ) |
There was a problem hiding this comment.
_parse_web_console_spec() now documents/CLI advertises --web-console [host[:port]] [password], but the parser requires a host:port pair (it raises when no ':' is present). This is a behavior/docs mismatch and prevents specifying just a host with default port. Allow host (no port) to default to 8000, or update the CLI/help text to require host:port.
Summary
Changes
Add a brief bullet list describing the key changes.
Testing
Describe how you tested the changes, including commands run or screenshots if relevant.
Checklist
CONTRIBUTING.md.LICENSE).