feat: auto-discover Power BI Desktop port, making -d optional on connect

This commit is contained in:
MinaSaad1 2026-03-26 16:38:44 +02:00
parent 62b2305909
commit 6f0f1afa61
3 changed files with 84 additions and 6 deletions

View file

@ -41,11 +41,13 @@ Install and set up pbi-cli from https://github.com/MinaSaad1/pbi-cli.git
**Or install manually (two commands):**
```bash
pipx install pbi-cli-tool # 1. Install (handles PATH automatically)
pbi connect -d localhost:54321 # 2. Connect (auto-downloads binary + installs skills)
pipx install pbi-cli-tool # 1. Install (handles PATH automatically)
pbi connect # 2. Auto-detects Power BI Desktop, downloads binary, installs skills
```
That's it. The first `connect` automatically downloads the MCP binary and installs Claude Code skills. Open Claude Code and start asking.
That's it. Open Power BI Desktop with a `.pbix` file, run `pbi connect`, and everything is set up automatically. Open Claude Code and start asking.
You can also specify the port manually: `pbi connect -d localhost:54321`
> **Requires:** Python 3.10+ and Power BI Desktop (local) or a Fabric workspace (cloud).

View file

@ -23,7 +23,12 @@ from pbi_cli.main import PbiContext, pass_context
@click.command()
@click.option("--data-source", "-d", required=True, help="Data source (e.g., localhost:54321).")
@click.option(
"--data-source",
"-d",
default=None,
help="Data source (e.g., localhost:54321). Auto-detected if omitted.",
)
@click.option("--catalog", "-C", default="", help="Initial catalog / dataset name.")
@click.option(
"--name", "-n", default=None, help="Name for this connection (auto-generated if omitted)."
@ -33,11 +38,21 @@ from pbi_cli.main import PbiContext, pass_context
)
@pass_context
def connect(
ctx: PbiContext, data_source: str, catalog: str, name: str | None, connection_string: str
ctx: PbiContext,
data_source: str | None,
catalog: str,
name: str | None,
connection_string: str,
) -> None:
"""Connect to a Power BI instance via data source."""
"""Connect to a Power BI instance via data source.
If --data-source is omitted, auto-detects a running Power BI Desktop instance.
"""
_ensure_ready()
if data_source is None:
data_source = _auto_discover_data_source()
conn_name = name or _auto_name(data_source)
request: dict[str, object] = {
@ -233,6 +248,27 @@ def connections_last(ctx: PbiContext) -> None:
)
def _auto_discover_data_source() -> str:
"""Auto-detect a running Power BI Desktop instance.
Raises click.ClickException if no instance is found.
"""
from pbi_cli.core.output import print_info
from pbi_cli.utils.platform import discover_pbi_port
port = discover_pbi_port()
if port is None:
raise click.ClickException(
"No running Power BI Desktop instance found.\n"
" 1. Open Power BI Desktop and load a .pbix file\n"
" 2. Run 'pbi connect' again, or specify manually: pbi connect -d localhost:<port>"
)
data_source = f"localhost:{port}"
print_info(f"Auto-detected Power BI Desktop on {data_source}")
return data_source
def _ensure_ready() -> None:
"""Auto-setup binary and skills if not already done.

View file

@ -57,6 +57,46 @@ def ensure_executable(path: Path) -> None:
path.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def discover_pbi_port() -> int | None:
"""Find the port of a running Power BI Desktop instance.
Power BI Desktop writes its XMLA port to a file at:
%LOCALAPPDATA%/Microsoft/Power BI Desktop/AnalysisServicesWorkspaces/
AnalysisServicesWorkspace_*/Data/msmdsrv.port.txt
Returns the port number, or None if Power BI Desktop is not running.
"""
if platform.system() != "Windows":
return None
import os
local_app_data = os.environ.get("LOCALAPPDATA", "")
if not local_app_data:
return None
workspaces_dir = (
Path(local_app_data) / "Microsoft" / "Power BI Desktop" / "AnalysisServicesWorkspaces"
)
if not workspaces_dir.exists():
return None
# Find all port files, pick the most recently modified (latest instance)
port_files = sorted(
workspaces_dir.glob("*/Data/msmdsrv.port.txt"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
if not port_files:
return None
try:
port_text = port_files[0].read_text(encoding="utf-8").strip()
return int(port_text)
except (ValueError, OSError):
return None
def find_vscode_extension_binary() -> Path | None:
"""Look for the binary in the VS Code extension install directory.