datasecops-cli 0.4.7__tar.gz → 0.4.8__tar.gz
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.
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/CHANGELOG.md +10 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/PKG-INFO +1 -1
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/pyproject.toml +1 -1
- datasecops_cli-0.4.8/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/main.py +33 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/menus/configuration.py +17 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/models/project_config.py +1 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/dbt_runner.py +63 -5
- datasecops_cli-0.4.7/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/.gitignore +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/LICENSE +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/README.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/docs/getting-started.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/docs/legacy.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/mcp-servers.json +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/setup.ps1 +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/setup.sh +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/__init__.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_config.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_main.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_models.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_version.py +0 -0
- {datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.8] - 2026-05-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **dbt Fusion update menu option** — new `[7] dbtf update` in the Configuration menu runs `dbtf system update` to upgrade dbt Fusion to the latest version. Checks that `dbtf` is on PATH before attempting the update.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`dbt docs generate` replaced with `dbt compile --write-catalog` for dbt Fusion** — dbt Fusion does not support `dbt docs generate`. The docs menu option now runs `dbtf compile --write-catalog` when using dbt Fusion, writing `catalog.json` to the target directory. Serving docs automatically downloads `index.html` from dbt-core if missing, launches `python -m http.server 8080` in a new terminal window, and opens the browser at `http://localhost:8080`. The CLI is not blocked — close the server terminal when done.
|
|
14
|
+
|
|
5
15
|
## [0.4.7] - 2026-05-19
|
|
6
16
|
|
|
7
17
|
### Fixed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.8"
|
|
@@ -196,6 +196,14 @@ def _run_setup(project_dir: Path):
|
|
|
196
196
|
if raw_sc:
|
|
197
197
|
source_control = SourceControl(**{k: v for k, v in raw_sc.items() if k in SourceControl.model_fields})
|
|
198
198
|
|
|
199
|
+
# Get account identifier for MCP URL construction
|
|
200
|
+
account_identifier = ""
|
|
201
|
+
try:
|
|
202
|
+
org_rows = temp_sf.execute_query("SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME() AS acct")
|
|
203
|
+
account_identifier = org_rows[0]["ACCT"].lower() if org_rows else ""
|
|
204
|
+
except Exception as e:
|
|
205
|
+
warning_line(f"Could not determine account identifier (AI Agent MCP will be skipped): {e}")
|
|
206
|
+
|
|
199
207
|
# --- Resolve profile name ---
|
|
200
208
|
from datasecops_cli.utilities.yaml_utils import get_profile_name
|
|
201
209
|
profile_name = get_profile_name(project_dir)
|
|
@@ -286,6 +294,11 @@ def _run_setup(project_dir: Path):
|
|
|
286
294
|
platform_choice = source_control.source_control_platform
|
|
287
295
|
info_line(f"Platform: {platform_choice}")
|
|
288
296
|
|
|
297
|
+
# Build AI Agent MCP URL once (used in both Cortex Code and mcp.json paths)
|
|
298
|
+
ai_agent_mcp_url = ""
|
|
299
|
+
if project_settings.ai_agents_enabled and account_identifier:
|
|
300
|
+
ai_agent_mcp_url = f"https://{account_identifier}.snowflakecomputing.com/api/v2/databases/{app_database}/schemas/AI/mcp-servers/DATASECOPS_MCP"
|
|
301
|
+
|
|
289
302
|
info_line("")
|
|
290
303
|
info_line("Which AI tool are you configuring?")
|
|
291
304
|
tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code"]
|
|
@@ -304,6 +317,18 @@ def _run_setup(project_dir: Path):
|
|
|
304
317
|
except FileNotFoundError:
|
|
305
318
|
warning_line("cortex not found — run manually: cortex mcp add datasecops-framework -- datasecops-mcp")
|
|
306
319
|
|
|
320
|
+
# Add AI Agent MCP server if enabled
|
|
321
|
+
if ai_agent_mcp_url:
|
|
322
|
+
try:
|
|
323
|
+
result = subprocess.run(["cortex", "mcp", "add", "datasecops-docs", ai_agent_mcp_url, "--transport", "http"],
|
|
324
|
+
capture_output=True, text=True)
|
|
325
|
+
if result.returncode == 0:
|
|
326
|
+
success_line("Added datasecops-docs MCP server (AI Agent documentation search)")
|
|
327
|
+
else:
|
|
328
|
+
warning_line(f"Failed to add AI Agent MCP server: {result.stderr.strip() or 'unknown error'}")
|
|
329
|
+
except Exception as agent_err:
|
|
330
|
+
warning_line(f"Could not add AI Agent MCP server: {agent_err}")
|
|
331
|
+
|
|
307
332
|
if platform_choice == "GitHub":
|
|
308
333
|
github_token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
|
|
309
334
|
if github_token:
|
|
@@ -343,6 +368,14 @@ def _run_setup(project_dir: Path):
|
|
|
343
368
|
|
|
344
369
|
servers = {"datasecops-framework": {"command": "datasecops-mcp", "args": []}}
|
|
345
370
|
|
|
371
|
+
# Add AI Agent MCP server if enabled
|
|
372
|
+
if ai_agent_mcp_url:
|
|
373
|
+
servers["datasecops-docs"] = {
|
|
374
|
+
"url": ai_agent_mcp_url,
|
|
375
|
+
"headers": {"Authorization": "Bearer <YOUR_PAT>"},
|
|
376
|
+
}
|
|
377
|
+
info_line("Note: Replace <YOUR_PAT> in datasecops-docs headers with your Snowflake PAT")
|
|
378
|
+
|
|
346
379
|
if platform_choice == "GitHub":
|
|
347
380
|
servers["github"] = {
|
|
348
381
|
"command": "npx",
|
|
@@ -51,6 +51,8 @@ class ConfigurationMenu:
|
|
|
51
51
|
self._cortex_upgrade()
|
|
52
52
|
elif option == 6:
|
|
53
53
|
self._toggle_dbt_engine()
|
|
54
|
+
elif option == 7:
|
|
55
|
+
self._dbtf_update()
|
|
54
56
|
self._menu()
|
|
55
57
|
option = get_input_number("Choose an option: ")
|
|
56
58
|
|
|
@@ -64,6 +66,7 @@ class ConfigurationMenu:
|
|
|
64
66
|
menu_option(4, "new project - Initialize a new dbt project with framework profiles")
|
|
65
67
|
menu_option(5, "cortex update - Update Cortex Code to the latest version")
|
|
66
68
|
menu_option(6, f"dbt engine - Switch dbt engine (current: {engine_label})")
|
|
69
|
+
menu_option(7, "dbtf update - Update dbt Fusion to the latest version")
|
|
67
70
|
menu_option(0, "back - Return to main menu")
|
|
68
71
|
|
|
69
72
|
def _install_dbt_requirements(self) -> None:
|
|
@@ -288,3 +291,17 @@ class ConfigurationMenu:
|
|
|
288
291
|
success_line(f"dbt engine switched to {self.datasecops_config.get_engine_label()} ({new_engine})")
|
|
289
292
|
warning_line("Restart the CLI for the change to take effect.")
|
|
290
293
|
complete_action()
|
|
294
|
+
|
|
295
|
+
def _dbtf_update(self) -> None:
|
|
296
|
+
"""Run dbtf system update to upgrade to the latest version."""
|
|
297
|
+
display_action_header("Update dbt Fusion")
|
|
298
|
+
if not shutil.which("dbtf"):
|
|
299
|
+
error_line("dbt Fusion (dbtf) not found. Install it first.")
|
|
300
|
+
complete_action()
|
|
301
|
+
return
|
|
302
|
+
try:
|
|
303
|
+
info_line("Checking for updates...")
|
|
304
|
+
subprocess.run(["dbtf", "system", "update"])
|
|
305
|
+
except FileNotFoundError:
|
|
306
|
+
error_line("dbt Fusion (dbtf) not found.")
|
|
307
|
+
complete_action()
|
|
@@ -29,6 +29,7 @@ class ProjectSettings(BaseModel):
|
|
|
29
29
|
project_dir: str = "./dbt"
|
|
30
30
|
profile_dir: str = "~/.dbt"
|
|
31
31
|
execution_mode: str = "dbt_cli"
|
|
32
|
+
ai_agents_enabled: bool = False
|
|
32
33
|
targets: list[DbtTarget] = Field(default_factory=lambda: [
|
|
33
34
|
DbtTarget(target_name="dev", branch_name="dev", target_role="DEVELOPERS", target_warehouse="DEV_WH", is_default=True),
|
|
34
35
|
DbtTarget(target_name="test", branch_name="test", target_role="DATAOPS_ADMIN", target_warehouse="DATAOPS_WH"),
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
+
import shlex
|
|
2
3
|
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
import urllib.request
|
|
6
|
+
import webbrowser
|
|
3
7
|
from pathlib import Path
|
|
4
8
|
from typing import Optional
|
|
5
9
|
|
|
6
|
-
from datasecops_cli.utilities.display import info_line, error_line, success_line
|
|
10
|
+
from datasecops_cli.utilities.display import info_line, error_line, success_line, warning_line
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
class DbtRunner:
|
|
@@ -93,12 +97,66 @@ class DbtRunner:
|
|
|
93
97
|
return self._run_command("source", ["freshness"] + args)
|
|
94
98
|
|
|
95
99
|
def docs_generate(self) -> subprocess.CompletedProcess:
|
|
100
|
+
if self.engine == "dbtf":
|
|
101
|
+
return self._run_command("compile", ["--write-catalog", f"--target={self.target}"])
|
|
96
102
|
return self._run_command("docs", ["generate", f"--target={self.target}"])
|
|
97
103
|
|
|
98
|
-
def docs_serve(self) ->
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
def docs_serve(self, port: int = 8080) -> None:
|
|
105
|
+
if self.engine == "dbtf":
|
|
106
|
+
target_dir = self.project_dir / "target"
|
|
107
|
+
index_file = target_dir / "index.html"
|
|
108
|
+
if not index_file.exists():
|
|
109
|
+
info_line("Downloading dbt docs index.html...")
|
|
110
|
+
url = "https://raw.githubusercontent.com/dbt-labs/dbt-core/main/core/dbt/task/docs/index.html"
|
|
111
|
+
try:
|
|
112
|
+
urllib.request.urlretrieve(url, str(index_file))
|
|
113
|
+
success_line("Downloaded index.html to target/")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
error_line(f"Failed to download index.html: {e}")
|
|
116
|
+
warning_line("Download manually from: https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/task/docs/index.html")
|
|
117
|
+
return
|
|
118
|
+
# Launch HTTP server in a new terminal window
|
|
119
|
+
cmd = [sys.executable, "-m", "http.server", str(port)]
|
|
120
|
+
try:
|
|
121
|
+
if sys.platform == "win32":
|
|
122
|
+
subprocess.Popen( # noqa: S603 - all args are internal/hardcoded
|
|
123
|
+
["cmd", "/c", "start", f"dbt docs - localhost:{port}"] + cmd,
|
|
124
|
+
cwd=str(target_dir),
|
|
125
|
+
)
|
|
126
|
+
elif sys.platform == "darwin":
|
|
127
|
+
subprocess.Popen( # noqa: S603 - all args are internal/hardcoded
|
|
128
|
+
["open", "-a", "Terminal", "--args"] + cmd,
|
|
129
|
+
cwd=str(target_dir),
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
# Linux — use shlex.quote and bash -lc for safe argument passing
|
|
133
|
+
cmd_str = " ".join(shlex.quote(arg) for arg in cmd)
|
|
134
|
+
for term in ("x-terminal-emulator", "gnome-terminal", "xterm"):
|
|
135
|
+
if not shutil.which(term):
|
|
136
|
+
continue
|
|
137
|
+
if term == "gnome-terminal":
|
|
138
|
+
term_cmd = [term, "--", "bash", "-lc", cmd_str]
|
|
139
|
+
else:
|
|
140
|
+
term_cmd = [term, "-e", "bash", "-lc", cmd_str]
|
|
141
|
+
subprocess.Popen(term_cmd, cwd=str(target_dir)) # noqa: S603
|
|
142
|
+
break
|
|
143
|
+
else:
|
|
144
|
+
# Fallback: run in background
|
|
145
|
+
subprocess.Popen(cmd, cwd=str(target_dir)) # noqa: S603
|
|
146
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
147
|
+
error_line(f"Failed to start docs server: {e}")
|
|
148
|
+
warning_line("Try manually: python -m http.server 8080 (from the target/ directory)")
|
|
149
|
+
return
|
|
150
|
+
info_line(f"Docs server started at http://localhost:{port}")
|
|
151
|
+
webbrowser.open(f"http://localhost:{port}")
|
|
152
|
+
success_line("Opened docs in browser")
|
|
153
|
+
else:
|
|
154
|
+
cmd = [self.engine, "docs", "serve"] + self._default_args()
|
|
155
|
+
info_line(f"Running: {' '.join(cmd)}")
|
|
156
|
+
try:
|
|
157
|
+
subprocess.Popen(cmd) # noqa: S603 - cmd is internal (engine + hardcoded args)
|
|
158
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
159
|
+
error_line(f"Failed to start docs server: {e}")
|
|
102
160
|
|
|
103
161
|
def clean(self) -> subprocess.CompletedProcess:
|
|
104
162
|
return self._run_command("clean")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.7 → datasecops_cli-0.4.8}/src/datasecops_cli/services/upstream_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|