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.
Files changed (57) hide show
  1. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/CHANGELOG.md +16 -0
  2. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/PKG-INFO +1 -1
  3. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/pyproject.toml +1 -1
  4. datasecops_cli-0.6.0/src/datasecops_cli/__init__.py +1 -0
  5. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/downloads.py +1 -1
  6. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/skill_service.py +67 -23
  7. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/file_utils.py +4 -0
  8. datasecops_cli-0.5.10/src/datasecops_cli/__init__.py +0 -1
  9. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/auto-tag.yml +0 -0
  10. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/publish-cli.yml +0 -0
  11. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.github/workflows/test.yml +0 -0
  12. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/.gitignore +0 -0
  13. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/DEVELOPMENT.md +0 -0
  14. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/LICENSE +0 -0
  15. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/README.md +0 -0
  16. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/getting-started.md +0 -0
  17. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/git-operations.md +0 -0
  18. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/legacy.md +0 -0
  19. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/legacy_plan_of_action.md +0 -0
  20. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/docs/mcp-server.md +0 -0
  21. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/mcp-servers.json +0 -0
  22. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/setup.ps1 +0 -0
  23. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/setup.sh +0 -0
  24. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/config.py +0 -0
  25. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/main.py +0 -0
  26. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/__init__.py +0 -0
  27. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/configuration.py +0 -0
  28. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/development.py +0 -0
  29. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/menus/git_operations.py +0 -0
  30. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/__init__.py +0 -0
  31. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/git_helpers.py +0 -0
  32. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/models/project_config.py +0 -0
  33. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/__init__.py +0 -0
  34. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  35. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  36. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/dbt_runner.py +0 -0
  37. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  38. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/download_service.py +0 -0
  39. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/git_service.py +0 -0
  40. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/linting_service.py +0 -0
  41. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/snowflake_service.py +0 -0
  42. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/services/upstream_service.py +0 -0
  43. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/__init__.py +0 -0
  44. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/display.py +0 -0
  45. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/mcp.py +0 -0
  46. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  47. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/__init__.py +0 -0
  48. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/__main__.py +0 -0
  49. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/connection.py +0 -0
  50. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/src/datasecops_mcp/server.py +0 -0
  51. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/__init__.py +0 -0
  52. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_config.py +0 -0
  53. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_file_utils.py +0 -0
  54. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_main.py +0 -0
  55. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_models.py +0 -0
  56. {datasecops_cli-0.5.10 → datasecops_cli-0.6.0}/tests/test_version.py +0 -0
  57. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.5.10
3
+ Version: 0.6.0
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.5.10"
7
+ version = "0.6.0"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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 - Cortex Code 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 = ["Cortex Code", "Cursor", "Claude Code"]
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. Cortex Code: .cortex/skills/{skill_id}/SKILL.md + extra files
72
- if "Cortex Code" in targets:
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("Cortex Code")
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
- write_file(claude_dir / f"{skill.skill_id}.md", skill_md)
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 "Cortex Code" in targets:
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 "Cortex Code" in targets:
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 _to_cursor_rule(skill: CortexSkill, skill_md_content: str) -> str:
150
- """Convert SKILL.md content to Cursor .mdc rule format.
151
-
152
- Cursor rules use the same markdown format but with optional
153
- frontmatter for description and globs.
154
- """
155
- # Strip the existing YAML frontmatter if present
156
- content = skill_md_content
157
- if content.startswith("---"):
158
- end = content.find("---", 3)
159
- if end != -1:
160
- content = content[end + 3:].lstrip("\n")
161
-
162
- # Add Cursor-style frontmatter
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"description: {skill.description}",
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."""
@@ -39,3 +39,7 @@ def get_cursor_rules_dir() -> Path:
39
39
 
40
40
  def get_claude_commands_dir() -> Path:
41
41
  return Path.cwd() / ".claude" / "commands"
42
+
43
+
44
+ def get_copilot_instructions_dir() -> Path:
45
+ return Path.cwd() / ".github" / "instructions"
@@ -1 +0,0 @@
1
- __version__ = "0.5.3"
File without changes
File without changes