praisonai-code 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"""Port management CLI command for PraisonAI.
|
|
2
|
+
|
|
3
|
+
Provides commands to manage port usage and resolve port conflicts:
|
|
4
|
+
- praisonai port list: List processes using ports
|
|
5
|
+
- praisonai port check <port>: Check if a port is in use
|
|
6
|
+
- praisonai port kill <port>: Kill process using a port
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
praisonai port list
|
|
10
|
+
praisonai port check 6379
|
|
11
|
+
praisonai port kill 6379
|
|
12
|
+
praisonai port kill 6379 --force
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
import typer
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(name="port", help="🔌 Manage port usage and resolve conflicts")
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_process_using_port(port: int) -> Optional[Dict]:
|
|
30
|
+
"""Get process information for a specific port.
|
|
31
|
+
|
|
32
|
+
Uses lsof on macOS/Linux and netstat on Windows.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
port: Port number to check
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with process info or None if port is free
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
if sys.platform == "darwin" or sys.platform.startswith("linux"):
|
|
42
|
+
# Use lsof on macOS and Linux
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
["lsof", "-i", f":{port}"],
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
timeout=5
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if result.returncode != 0 or not result.stdout:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Parse lsof output
|
|
54
|
+
lines = result.stdout.strip().split("\n")
|
|
55
|
+
if len(lines) < 2:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# First line is header, get first data line
|
|
59
|
+
data_line = lines[1]
|
|
60
|
+
parts = data_line.split()
|
|
61
|
+
|
|
62
|
+
if len(parts) >= 9:
|
|
63
|
+
return {
|
|
64
|
+
"name": parts[0],
|
|
65
|
+
"pid": parts[1],
|
|
66
|
+
"user": parts[2],
|
|
67
|
+
"type": parts[4],
|
|
68
|
+
"protocol": parts[7] if len(parts) > 7 else "TCP",
|
|
69
|
+
"port": port,
|
|
70
|
+
"raw": data_line
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
elif sys.platform == "win32":
|
|
74
|
+
# Use netstat on Windows
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
["netstat", "-ano"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
timeout=10
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if result.returncode != 0 or not result.stdout:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
# Parse netstat output and filter by exact port
|
|
86
|
+
lines = result.stdout.strip().split("\n")
|
|
87
|
+
for line in lines:
|
|
88
|
+
parts = line.split()
|
|
89
|
+
if len(parts) >= 5 and "LISTENING" in line:
|
|
90
|
+
local_addr = parts[1]
|
|
91
|
+
# Extract port from address, handle IPv6 format [::]:port and IPv4 format host:port
|
|
92
|
+
port_match = re.search(r":(\d+)$", local_addr)
|
|
93
|
+
if not port_match or int(port_match.group(1)) != port:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
pid = parts[-1]
|
|
97
|
+
# Get process name from PID
|
|
98
|
+
try:
|
|
99
|
+
proc_result = subprocess.run(
|
|
100
|
+
["tasklist", "/FI", f"PID eq {pid}"],
|
|
101
|
+
capture_output=True,
|
|
102
|
+
text=True,
|
|
103
|
+
timeout=5
|
|
104
|
+
)
|
|
105
|
+
name = "unknown"
|
|
106
|
+
if proc_result.returncode == 0:
|
|
107
|
+
proc_lines = proc_result.stdout.strip().split("\n")
|
|
108
|
+
if len(proc_lines) >= 2:
|
|
109
|
+
name = proc_lines[1].split()[0]
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"name": name,
|
|
113
|
+
"pid": pid,
|
|
114
|
+
"user": "unknown",
|
|
115
|
+
"type": "IPv4",
|
|
116
|
+
"protocol": "TCP",
|
|
117
|
+
"port": port,
|
|
118
|
+
"raw": line
|
|
119
|
+
}
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
except subprocess.TimeoutExpired:
|
|
124
|
+
pass
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_all_ports() -> List[Dict]:
|
|
132
|
+
"""Get list of all processes using network ports.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of dicts with process information
|
|
136
|
+
"""
|
|
137
|
+
processes = []
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
if sys.platform == "darwin" or sys.platform.startswith("linux"):
|
|
141
|
+
result = subprocess.run(
|
|
142
|
+
["lsof", "-i", "-P", "-n"],
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
timeout=10
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if result.returncode == 0:
|
|
149
|
+
lines = result.stdout.strip().split("\n")
|
|
150
|
+
# Skip header line
|
|
151
|
+
for line in lines[1:]:
|
|
152
|
+
parts = line.split()
|
|
153
|
+
if len(parts) >= 9:
|
|
154
|
+
# Extract port from address
|
|
155
|
+
addr = parts[8] if len(parts) > 8 else ""
|
|
156
|
+
port_match = re.search(r":(\d+)", addr)
|
|
157
|
+
if port_match:
|
|
158
|
+
port = int(port_match.group(1))
|
|
159
|
+
processes.append({
|
|
160
|
+
"name": parts[0],
|
|
161
|
+
"pid": parts[1],
|
|
162
|
+
"user": parts[2],
|
|
163
|
+
"protocol": parts[7] if len(parts) > 7 else "TCP",
|
|
164
|
+
"port": port,
|
|
165
|
+
"address": addr
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
elif sys.platform == "win32":
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
["netstat", "-ano"],
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
timeout=10
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if result.returncode == 0:
|
|
177
|
+
lines = result.stdout.strip().split("\n")
|
|
178
|
+
for line in lines[4:]: # Skip header lines
|
|
179
|
+
parts = line.split()
|
|
180
|
+
if len(parts) >= 5 and ("LISTENING" in line or "ESTABLISHED" in line):
|
|
181
|
+
local_addr = parts[1]
|
|
182
|
+
pid = parts[4]
|
|
183
|
+
port_match = re.search(r":(\d+)", local_addr)
|
|
184
|
+
if port_match:
|
|
185
|
+
port = int(port_match.group(1))
|
|
186
|
+
processes.append({
|
|
187
|
+
"name": f"PID:{pid}",
|
|
188
|
+
"pid": pid,
|
|
189
|
+
"user": "unknown",
|
|
190
|
+
"protocol": parts[0],
|
|
191
|
+
"port": port,
|
|
192
|
+
"address": local_addr
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
except subprocess.TimeoutExpired:
|
|
196
|
+
pass
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
return processes
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _kill_process(pid: str, force: bool = False) -> bool:
|
|
204
|
+
"""Kill a process by PID.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
pid: Process ID to kill
|
|
208
|
+
force: If True, use SIGKILL (-9) instead of SIGTERM
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if successful, False otherwise
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
if sys.platform == "win32":
|
|
215
|
+
# Windows
|
|
216
|
+
cmd = ["taskkill", "/F", "/PID", str(pid)] if force else ["taskkill", "/PID", str(pid)]
|
|
217
|
+
result = subprocess.run(cmd, capture_output=True, timeout=5)
|
|
218
|
+
return result.returncode == 0
|
|
219
|
+
else:
|
|
220
|
+
# macOS/Linux
|
|
221
|
+
signal = "-9" if force else "-15"
|
|
222
|
+
result = subprocess.run(["kill", signal, str(pid)], capture_output=True, timeout=5)
|
|
223
|
+
return result.returncode == 0
|
|
224
|
+
except subprocess.TimeoutExpired:
|
|
225
|
+
return False
|
|
226
|
+
except Exception:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _is_port_conflict_error(error_msg: str) -> bool:
|
|
231
|
+
"""Check if error message indicates a port conflict.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
error_msg: Error message to check
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
True if it's a port conflict error
|
|
238
|
+
"""
|
|
239
|
+
port_conflict_patterns = [
|
|
240
|
+
"address already in use",
|
|
241
|
+
"port is already allocated",
|
|
242
|
+
"bind: address already in use",
|
|
243
|
+
"Ports are not available",
|
|
244
|
+
"port.*already.*use",
|
|
245
|
+
"listen tcp.*bind: address already in use"
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
error_lower = error_msg.lower()
|
|
249
|
+
for pattern in port_conflict_patterns:
|
|
250
|
+
if pattern.lower() in error_lower:
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _extract_port_from_error(error_msg: str) -> Optional[int]:
|
|
257
|
+
"""Extract port number from error message.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
error_msg: Error message containing port info
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Port number if found, None otherwise
|
|
264
|
+
"""
|
|
265
|
+
# Common patterns for port mentions
|
|
266
|
+
patterns = [
|
|
267
|
+
r":(\d+)->", # Docker style: 127.0.0.1:6379->0.0.0.0:0
|
|
268
|
+
r":(\d+)", # Generic port format
|
|
269
|
+
r"port\s+(\d+)", # "port 6379" format
|
|
270
|
+
r"tcp\s+.*:(\d+)", # TCP with port
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
for pattern in patterns:
|
|
274
|
+
match = re.search(pattern, error_msg)
|
|
275
|
+
if match:
|
|
276
|
+
try:
|
|
277
|
+
return int(match.group(1))
|
|
278
|
+
except ValueError:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _show_port_conflict_help(port: int, console: Optional[Console] = None) -> None:
|
|
285
|
+
"""Show helpful message when port conflict is detected.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
port: Port number that caused conflict
|
|
289
|
+
console: Console to print to (creates new if None)
|
|
290
|
+
"""
|
|
291
|
+
if console is None:
|
|
292
|
+
console = Console()
|
|
293
|
+
|
|
294
|
+
# Get process info
|
|
295
|
+
proc = _get_process_using_port(port)
|
|
296
|
+
|
|
297
|
+
console.print()
|
|
298
|
+
console.print(Panel(
|
|
299
|
+
f"[bold yellow]Port {port} is already in use[/bold yellow]\n\n"
|
|
300
|
+
f"Another process is using port {port}, preventing the service from starting.",
|
|
301
|
+
title="🔌 Port Conflict",
|
|
302
|
+
border_style="red"
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
if proc:
|
|
306
|
+
console.print(f"\n[bold]Process using port {port}:[/bold]")
|
|
307
|
+
console.print(f" Name: {proc['name']}")
|
|
308
|
+
console.print(f" PID: {proc['pid']}")
|
|
309
|
+
console.print(f" User: {proc['user']}")
|
|
310
|
+
|
|
311
|
+
console.print("\n[bold cyan]How to resolve:[/bold cyan]")
|
|
312
|
+
console.print()
|
|
313
|
+
|
|
314
|
+
# Create a table of options
|
|
315
|
+
table = Table(show_header=False, border_style="dim")
|
|
316
|
+
table.add_column("Option", style="green", no_wrap=True)
|
|
317
|
+
table.add_column("Command", style="cyan")
|
|
318
|
+
table.add_column("Description", style="white")
|
|
319
|
+
|
|
320
|
+
table.add_row(
|
|
321
|
+
"1. Kill process",
|
|
322
|
+
f"praisonai port kill {port}",
|
|
323
|
+
"Kill the process using this port"
|
|
324
|
+
)
|
|
325
|
+
table.add_row(
|
|
326
|
+
"2. Force kill",
|
|
327
|
+
f"praisonai port kill {port} --force",
|
|
328
|
+
"Force kill if graceful kill fails"
|
|
329
|
+
)
|
|
330
|
+
table.add_row(
|
|
331
|
+
"3. Check details",
|
|
332
|
+
f"praisonai port check {port}",
|
|
333
|
+
"See process details before killing"
|
|
334
|
+
)
|
|
335
|
+
table.add_row(
|
|
336
|
+
"4. List all ports",
|
|
337
|
+
"praisonai port list",
|
|
338
|
+
"See all processes using ports"
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
console.print(table)
|
|
342
|
+
|
|
343
|
+
console.print("\n[dim]Or manually:[/dim]")
|
|
344
|
+
if sys.platform == "darwin" or sys.platform.startswith("linux"):
|
|
345
|
+
console.print(f" [green]lsof -i :{port}[/green] # See process details")
|
|
346
|
+
console.print(" [green]kill -9 <PID>[/green] # Kill by PID")
|
|
347
|
+
elif sys.platform == "win32":
|
|
348
|
+
console.print(f" [green]netstat -ano | findstr :{port}[/green] # See process details")
|
|
349
|
+
console.print(" [green]taskkill /F /PID <PID>[/green] # Kill by PID")
|
|
350
|
+
|
|
351
|
+
console.print()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@app.callback(invoke_without_command=True)
|
|
355
|
+
def port_callback(ctx: typer.Context):
|
|
356
|
+
"""Manage port usage and resolve conflicts."""
|
|
357
|
+
if ctx.invoked_subcommand is None:
|
|
358
|
+
# Default action: show help
|
|
359
|
+
console.print("[bold]Port Management Commands[/bold]\n")
|
|
360
|
+
console.print("Use [cyan]praisonai port list[/cyan] to see all ports in use")
|
|
361
|
+
console.print("Use [cyan]praisonai port check <port>[/cyan] to check a specific port")
|
|
362
|
+
console.print("Use [cyan]praisonai port kill <port>[/cyan] to free a port\n")
|
|
363
|
+
raise typer.Exit()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@app.command("list")
|
|
367
|
+
def port_list(
|
|
368
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show all details"),
|
|
369
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
370
|
+
):
|
|
371
|
+
"""List all processes using network ports."""
|
|
372
|
+
processes = _get_all_ports()
|
|
373
|
+
|
|
374
|
+
if not processes:
|
|
375
|
+
console.print("[yellow]No processes found using network ports.[/yellow]")
|
|
376
|
+
console.print("[dim](May require elevated permissions on some systems)[/dim]")
|
|
377
|
+
raise typer.Exit(0)
|
|
378
|
+
|
|
379
|
+
if json_output:
|
|
380
|
+
import json
|
|
381
|
+
console.print(json.dumps(processes, indent=2))
|
|
382
|
+
raise typer.Exit(0)
|
|
383
|
+
|
|
384
|
+
# Create table
|
|
385
|
+
table = Table(title=f"🔌 Processes Using Network Ports ({len(processes)} found)")
|
|
386
|
+
table.add_column("Process", style="cyan", no_wrap=True)
|
|
387
|
+
table.add_column("PID", style="yellow")
|
|
388
|
+
table.add_column("Port", style="green", justify="right")
|
|
389
|
+
table.add_column("Protocol", style="blue")
|
|
390
|
+
table.add_column("User", style="magenta")
|
|
391
|
+
|
|
392
|
+
if verbose:
|
|
393
|
+
table.add_column("Address", style="dim")
|
|
394
|
+
|
|
395
|
+
# Sort by port number
|
|
396
|
+
sorted_procs = sorted(processes, key=lambda x: x.get("port", 0))
|
|
397
|
+
|
|
398
|
+
for proc in sorted_procs:
|
|
399
|
+
row = [
|
|
400
|
+
proc.get("name", "unknown"),
|
|
401
|
+
proc.get("pid", "-"),
|
|
402
|
+
str(proc.get("port", "-")),
|
|
403
|
+
proc.get("protocol", "TCP"),
|
|
404
|
+
proc.get("user", "unknown"),
|
|
405
|
+
]
|
|
406
|
+
if verbose:
|
|
407
|
+
row.append(proc.get("address", "-"))
|
|
408
|
+
table.add_row(*row)
|
|
409
|
+
|
|
410
|
+
console.print(table)
|
|
411
|
+
console.print()
|
|
412
|
+
console.print(f"[dim]Tip: Use [cyan]praisonai port kill <port>[/cyan] to free a port[/dim]")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@app.command("check")
|
|
416
|
+
def port_check(
|
|
417
|
+
port: int = typer.Argument(..., help="Port number to check"),
|
|
418
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
419
|
+
):
|
|
420
|
+
"""Check if a specific port is in use."""
|
|
421
|
+
proc = _get_process_using_port(port)
|
|
422
|
+
|
|
423
|
+
if json_output:
|
|
424
|
+
import json
|
|
425
|
+
if proc:
|
|
426
|
+
console.print(json.dumps({"in_use": True, "process": proc}, indent=2))
|
|
427
|
+
else:
|
|
428
|
+
console.print(json.dumps({"in_use": False}, indent=2))
|
|
429
|
+
raise typer.Exit(0)
|
|
430
|
+
|
|
431
|
+
if proc:
|
|
432
|
+
console.print(f"[red]✗ Port {port} is in use[/red]")
|
|
433
|
+
console.print()
|
|
434
|
+
|
|
435
|
+
table = Table(title=f"Process Using Port {port}", border_style="red")
|
|
436
|
+
table.add_column("Field", style="cyan")
|
|
437
|
+
table.add_column("Value", style="white")
|
|
438
|
+
|
|
439
|
+
table.add_row("Process Name", proc["name"])
|
|
440
|
+
table.add_row("PID", proc["pid"])
|
|
441
|
+
table.add_row("User", proc["user"])
|
|
442
|
+
table.add_row("Protocol", proc["protocol"])
|
|
443
|
+
table.add_row("Type", proc["type"])
|
|
444
|
+
|
|
445
|
+
console.print(table)
|
|
446
|
+
console.print()
|
|
447
|
+
console.print(f"To free this port: [cyan]praisonai port kill {port}[/cyan]")
|
|
448
|
+
raise typer.Exit(1)
|
|
449
|
+
else:
|
|
450
|
+
console.print(f"[green]✓ Port {port} is available[/green]")
|
|
451
|
+
raise typer.Exit(0)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@app.command("kill")
|
|
455
|
+
def port_kill(
|
|
456
|
+
port: int = typer.Argument(..., help="Port number to free"),
|
|
457
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force kill (SIGKILL)"),
|
|
458
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
459
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
460
|
+
):
|
|
461
|
+
"""Kill the process using a specific port."""
|
|
462
|
+
proc = _get_process_using_port(port)
|
|
463
|
+
|
|
464
|
+
if not proc:
|
|
465
|
+
if json_output:
|
|
466
|
+
import json
|
|
467
|
+
console.print(json.dumps({"success": False, "error": f"No process found using port {port}"}, indent=2))
|
|
468
|
+
else:
|
|
469
|
+
console.print(f"[yellow]âš No process found using port {port}[/yellow]")
|
|
470
|
+
console.print("[dim]Port may already be free.[/dim]")
|
|
471
|
+
raise typer.Exit(0)
|
|
472
|
+
|
|
473
|
+
# Show what we're about to kill
|
|
474
|
+
if not json_output:
|
|
475
|
+
console.print(f"\n[bold]Process using port {port}:[/bold]")
|
|
476
|
+
console.print(f" Name: {proc['name']}")
|
|
477
|
+
console.print(f" PID: {proc['pid']}")
|
|
478
|
+
console.print(f" User: {proc['user']}")
|
|
479
|
+
console.print()
|
|
480
|
+
|
|
481
|
+
# Confirm unless --yes
|
|
482
|
+
if not yes and not json_output:
|
|
483
|
+
if not typer.confirm(f"Kill process {proc['name']} (PID {proc['pid']})?"):
|
|
484
|
+
console.print("[yellow]Aborted.[/yellow]")
|
|
485
|
+
raise typer.Exit(0)
|
|
486
|
+
|
|
487
|
+
# Kill the process
|
|
488
|
+
success = _kill_process(proc["pid"], force=force)
|
|
489
|
+
|
|
490
|
+
if success:
|
|
491
|
+
if json_output:
|
|
492
|
+
import json
|
|
493
|
+
console.print(json.dumps({
|
|
494
|
+
"success": True,
|
|
495
|
+
"port": port,
|
|
496
|
+
"process": proc["name"],
|
|
497
|
+
"pid": proc["pid"]
|
|
498
|
+
}, indent=2))
|
|
499
|
+
else:
|
|
500
|
+
signal_type = "force killed" if force else "killed"
|
|
501
|
+
console.print(f"[green]✓ Process {proc['name']} (PID {proc['pid']}) {signal_type}[/green]")
|
|
502
|
+
console.print(f"[green]✓ Port {port} is now free[/green]")
|
|
503
|
+
raise typer.Exit(0)
|
|
504
|
+
else:
|
|
505
|
+
if json_output:
|
|
506
|
+
import json
|
|
507
|
+
console.print(json.dumps({
|
|
508
|
+
"success": False,
|
|
509
|
+
"error": f"Failed to kill process {proc['pid']}"
|
|
510
|
+
}, indent=2))
|
|
511
|
+
else:
|
|
512
|
+
console.print(f"[red]✗ Failed to kill process {proc['pid']}[/red]")
|
|
513
|
+
if not force:
|
|
514
|
+
console.print("[yellow]Try: praisonai port kill {port} --force[/yellow]")
|
|
515
|
+
raise typer.Exit(1)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# Export utility functions for use in other modules
|
|
519
|
+
__all__ = [
|
|
520
|
+
"app",
|
|
521
|
+
"_is_port_conflict_error",
|
|
522
|
+
"_extract_port_from_error",
|
|
523
|
+
"_show_port_conflict_help",
|
|
524
|
+
"_get_process_using_port",
|
|
525
|
+
"_kill_process",
|
|
526
|
+
"_get_all_ports",
|
|
527
|
+
"port_list",
|
|
528
|
+
"port_check",
|
|
529
|
+
"port_kill",
|
|
530
|
+
]
|