datasecops-cli 0.5.10__tar.gz → 0.6.0__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.5.10 → datasecops_cli-0.6.0}/CHANGELOG.md +16 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/PKG-INFO +1 -1
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/pyproject.toml +1 -1
- datasecops_cli-0.6.0/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/downloads.py +1 -1
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/skill_service.py +67 -23
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/file_utils.py +4 -0
- datasecops_cli-0.5.10/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/test.yml +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.gitignore +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/LICENSE +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/README.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/getting-started.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/git-operations.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/legacy.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/mcp-servers.json +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/setup.ps1 +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/setup.sh +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/main.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/configuration.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/mcp.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/__init__.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_config.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_main.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_models.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_version.py +0 -0
- {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.0] - 2026-07-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **GitHub Copilot skill target** — skills can now be installed as `.github/instructions/<id>.instructions.md` files with `applyTo` frontmatter for GitHub Copilot custom instructions.
|
|
10
|
+
- **Multi-tool skill download** — the skill install menu now prompts users to select target tools: CoCo, Cursor, Copilot, and/or Claude Code. Each format is automatically generated from the same source skill.
|
|
11
|
+
- **Snowflake query instructions** — Cursor, Copilot, and Claude skill files automatically include instructions on how to query Snowflake via the `snow` CLI when the skill contains SQL blocks.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Skill target renamed** — "Cortex Code" target label renamed to "CoCo" to match the native app UI naming convention.
|
|
16
|
+
- **Downloads menu label** — "Cortex Code skills" renamed to "AI coding skills".
|
|
17
|
+
- **Shared conversion helpers** — extracted `_strip_frontmatter()` and `_prepare_body()` to eliminate duplication across format converters.
|
|
18
|
+
- **Robust frontmatter stripping** — uses line-boundary matching (`\n---`) instead of simple `split('---', 2)` to avoid false matches in skill body content.
|
|
19
|
+
- **Cursor description quoting** — `.mdc` description values are now properly quoted and sanitized (newlines removed, quotes escaped).
|
|
20
|
+
|
|
5
21
|
## [0.5.10] - 2026-06-26
|
|
6
22
|
|
|
7
23
|
### Changed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -70,7 +70,7 @@ class DownloadsMenu:
|
|
|
70
70
|
menu_option(2, "pipelines - Download CI/CD pipeline files")
|
|
71
71
|
menu_option(3, "dbt packages - Download dbt package versions")
|
|
72
72
|
menu_option(4, "macros - Download default macros for this profile")
|
|
73
|
-
menu_option(5, "skills -
|
|
73
|
+
menu_option(5, "skills - AI coding skills")
|
|
74
74
|
menu_option(0, "back - Return to main menu")
|
|
75
75
|
|
|
76
76
|
def _skills_menu(self) -> None:
|
|
@@ -7,14 +7,26 @@ from datasecops_cli.models.project_config import CortexSkill, CortexSkillFile
|
|
|
7
7
|
from datasecops_cli.utilities.display import info_line, success_line, error_line, warning_line
|
|
8
8
|
from datasecops_cli.utilities.file_utils import (
|
|
9
9
|
write_file, ensure_dir, get_cortex_skills_dir,
|
|
10
|
-
get_cursor_rules_dir, get_claude_commands_dir,
|
|
10
|
+
get_cursor_rules_dir, get_claude_commands_dir, get_copilot_instructions_dir,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
+
_SNOWFLAKE_QUERY_INSTRUCTIONS = """## Querying Snowflake
|
|
14
|
+
|
|
15
|
+
This skill requires running SQL queries against Snowflake to retrieve configuration.
|
|
16
|
+
Use the Snowflake CLI (`snow`) to execute queries:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
snow sql -q "YOUR SQL QUERY HERE" -c <connection_name>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Alternatively, if an MCP server for Snowflake is configured, use it to execute queries directly.
|
|
23
|
+
"""
|
|
24
|
+
|
|
13
25
|
|
|
14
26
|
class SkillService:
|
|
15
27
|
"""Manages skill installation for Cortex Code, Cursor, and Claude Code."""
|
|
16
28
|
|
|
17
|
-
TARGETS = ["
|
|
29
|
+
TARGETS = ["CoCo", "Cursor", "Copilot", "Claude Code"]
|
|
18
30
|
|
|
19
31
|
def __init__(self, snowflake_service: SnowflakeService):
|
|
20
32
|
self.sf = snowflake_service
|
|
@@ -68,15 +80,15 @@ class SkillService:
|
|
|
68
80
|
targets = self.TARGETS
|
|
69
81
|
targets_installed: list[str] = []
|
|
70
82
|
|
|
71
|
-
# 1.
|
|
72
|
-
if "
|
|
83
|
+
# 1. CoCo: .cortex/skills/{skill_id}/SKILL.md + extra files
|
|
84
|
+
if "CoCo" in targets:
|
|
73
85
|
cortex_dir = target_dir or get_cortex_skills_dir()
|
|
74
86
|
skill_dir = cortex_dir / skill.skill_id
|
|
75
87
|
ensure_dir(skill_dir)
|
|
76
88
|
for f in skill.files:
|
|
77
89
|
if f.filename and f.content:
|
|
78
90
|
write_file(skill_dir / f.filename, f.content)
|
|
79
|
-
targets_installed.append("
|
|
91
|
+
targets_installed.append("CoCo")
|
|
80
92
|
|
|
81
93
|
# Extract SKILL.md content for Cursor and Claude Code
|
|
82
94
|
skill_md = self._get_skill_md_content(skill)
|
|
@@ -97,11 +109,23 @@ class SkillService:
|
|
|
97
109
|
try:
|
|
98
110
|
claude_dir = get_claude_commands_dir()
|
|
99
111
|
ensure_dir(claude_dir)
|
|
100
|
-
|
|
112
|
+
claude_content = self._to_claude_command(skill, skill_md)
|
|
113
|
+
write_file(claude_dir / f"{skill.skill_id}.md", claude_content)
|
|
101
114
|
targets_installed.append("Claude Code")
|
|
102
115
|
except OSError as e:
|
|
103
116
|
warning_line(f" Claude Code install failed: {e}")
|
|
104
117
|
|
|
118
|
+
# 4. GitHub Copilot: .github/instructions/{skill_id}.instructions.md
|
|
119
|
+
if "Copilot" in targets and skill_md:
|
|
120
|
+
try:
|
|
121
|
+
copilot_dir = get_copilot_instructions_dir()
|
|
122
|
+
ensure_dir(copilot_dir)
|
|
123
|
+
copilot_content = self._to_copilot_instructions(skill, skill_md)
|
|
124
|
+
write_file(copilot_dir / f"{skill.skill_id}.instructions.md", copilot_content)
|
|
125
|
+
targets_installed.append("Copilot")
|
|
126
|
+
except OSError as e:
|
|
127
|
+
warning_line(f" Copilot install failed: {e}")
|
|
128
|
+
|
|
105
129
|
success_line(f"Installed skill: {skill.name} v{skill.version} -> {', '.join(targets_installed)}")
|
|
106
130
|
return True
|
|
107
131
|
|
|
@@ -113,7 +137,7 @@ class SkillService:
|
|
|
113
137
|
for skill in skills:
|
|
114
138
|
if self.install_skill(skill, targets=targets, target_dir=target_dir):
|
|
115
139
|
count += 1
|
|
116
|
-
if "
|
|
140
|
+
if "CoCo" in targets:
|
|
117
141
|
self._register_cortex_skills_dir(target_dir)
|
|
118
142
|
success_line(f"Installed {count} skill(s) to {', '.join(targets)}")
|
|
119
143
|
return count
|
|
@@ -128,7 +152,7 @@ class SkillService:
|
|
|
128
152
|
if self._is_installed(skill.skill_id, target):
|
|
129
153
|
self.install_skill(skill, targets=targets, target_dir=target_dir)
|
|
130
154
|
count += 1
|
|
131
|
-
if "
|
|
155
|
+
if "CoCo" in targets:
|
|
132
156
|
self._register_cortex_skills_dir(target_dir)
|
|
133
157
|
success_line(f"Updated {count} skill(s)")
|
|
134
158
|
return count
|
|
@@ -146,23 +170,32 @@ class SkillService:
|
|
|
146
170
|
return ""
|
|
147
171
|
|
|
148
172
|
@staticmethod
|
|
149
|
-
def
|
|
150
|
-
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
content
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
def _strip_frontmatter(content: str) -> str:
|
|
174
|
+
"""Remove leading YAML frontmatter (---...---) from markdown content."""
|
|
175
|
+
if not content.startswith("---"):
|
|
176
|
+
return content
|
|
177
|
+
end = content.find("\n---", 3)
|
|
178
|
+
if end == -1:
|
|
179
|
+
return content
|
|
180
|
+
return content[end + 4:].lstrip("\n")
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def _prepare_body(cls, skill_md_content: str) -> str:
|
|
184
|
+
"""Strip frontmatter and prepend Snowflake query instructions if needed."""
|
|
185
|
+
body = cls._strip_frontmatter(skill_md_content)
|
|
186
|
+
if "```sql" in body:
|
|
187
|
+
body = _SNOWFLAKE_QUERY_INSTRUCTIONS + "\n" + body
|
|
188
|
+
return body
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def _to_cursor_rule(cls, skill: CortexSkill, skill_md_content: str) -> str:
|
|
192
|
+
"""Convert SKILL.md content to Cursor .mdc rule format."""
|
|
193
|
+
content = cls._prepare_body(skill_md_content)
|
|
194
|
+
# Sanitize description for single-line YAML value
|
|
195
|
+
description = (skill.description or "").replace("\n", " ").replace('"', '\\"')
|
|
163
196
|
lines = [
|
|
164
197
|
"---",
|
|
165
|
-
f
|
|
198
|
+
f'description: "{description}"',
|
|
166
199
|
"globs: []",
|
|
167
200
|
"alwaysApply: false",
|
|
168
201
|
"---",
|
|
@@ -171,6 +204,17 @@ class SkillService:
|
|
|
171
204
|
]
|
|
172
205
|
return "\n".join(lines)
|
|
173
206
|
|
|
207
|
+
@classmethod
|
|
208
|
+
def _to_claude_command(cls, skill: CortexSkill, skill_md_content: str) -> str:
|
|
209
|
+
"""Convert SKILL.md content to Claude Code command format."""
|
|
210
|
+
return cls._prepare_body(skill_md_content)
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def _to_copilot_instructions(cls, skill: CortexSkill, skill_md_content: str) -> str:
|
|
214
|
+
"""Convert SKILL.md content to GitHub Copilot instructions format."""
|
|
215
|
+
content = cls._prepare_body(skill_md_content)
|
|
216
|
+
return f'---\napplyTo: "**"\n---\n\n{content}'
|
|
217
|
+
|
|
174
218
|
@staticmethod
|
|
175
219
|
def _register_cortex_skills_dir(target_dir: Path = None) -> None:
|
|
176
220
|
"""Register the skills directory with Cortex Code CLI so skills are discoverable."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.3"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/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
|