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.
Files changed (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. 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
+ ]