karcytics-sdk 2.0.0__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 (56) hide show
  1. karcytics_sdk/__init__.py +10 -0
  2. karcytics_sdk/cli/__init__.py +0 -0
  3. karcytics_sdk/cli/commands/__init__.py +0 -0
  4. karcytics_sdk/cli/commands/diagnostics.py +81 -0
  5. karcytics_sdk/cli/commands/migrate.py +112 -0
  6. karcytics_sdk/cli/commands/scaffold.py +450 -0
  7. karcytics_sdk/cli/commands/security.py +109 -0
  8. karcytics_sdk/cli/main.py +43 -0
  9. karcytics_sdk/contrib/__init__.py +38 -0
  10. karcytics_sdk/contrib/image_utils.py +682 -0
  11. karcytics_sdk/host/__init__.py +40 -0
  12. karcytics_sdk/host/ai.py +421 -0
  13. karcytics_sdk/host/core_services.py +160 -0
  14. karcytics_sdk/host/docs.py +45 -0
  15. karcytics_sdk/host/marketplace_cache.py +157 -0
  16. karcytics_sdk/host/sign_plugin.py +478 -0
  17. karcytics_sdk/host/trust_manager.py +487 -0
  18. karcytics_sdk/host/trust_overrides.py +113 -0
  19. karcytics_sdk/host/trust_path.py +71 -0
  20. karcytics_sdk/host/trust_storage.py +102 -0
  21. karcytics_sdk/interfaces/__init__.py +5 -0
  22. karcytics_sdk/interfaces/i_event_bus.py +15 -0
  23. karcytics_sdk/interfaces/i_logger.py +26 -0
  24. karcytics_sdk/interfaces/i_task_scheduler.py +17 -0
  25. karcytics_sdk/plugin/__init__.py +137 -0
  26. karcytics_sdk/plugin/analysis.py +207 -0
  27. karcytics_sdk/plugin/base.py +347 -0
  28. karcytics_sdk/plugin/components.py +671 -0
  29. karcytics_sdk/plugin/context.py +29 -0
  30. karcytics_sdk/plugin/daemon.py +693 -0
  31. karcytics_sdk/plugin/dialogs.py +259 -0
  32. karcytics_sdk/plugin/events.py +85 -0
  33. karcytics_sdk/plugin/interfaces.py +46 -0
  34. karcytics_sdk/plugin/io.py +189 -0
  35. karcytics_sdk/plugin/logging.py +53 -0
  36. karcytics_sdk/plugin/managed_task.py +48 -0
  37. karcytics_sdk/plugin/manifest.py +32 -0
  38. karcytics_sdk/plugin/manifest_parser.py +78 -0
  39. karcytics_sdk/plugin/preferences.py +37 -0
  40. karcytics_sdk/plugin/ribbon.py +81 -0
  41. karcytics_sdk/plugin/security_parser.py +78 -0
  42. karcytics_sdk/plugin/signals.py +45 -0
  43. karcytics_sdk/plugin/state.py +57 -0
  44. karcytics_sdk/plugin/theme_fallback.py +95 -0
  45. karcytics_sdk/plugin/validation.py +114 -0
  46. karcytics_sdk/plugin/wizard.py +443 -0
  47. karcytics_sdk/plugin/workflow.py +74 -0
  48. karcytics_sdk/py.typed +1 -0
  49. karcytics_sdk/sdk_cli.py +119 -0
  50. karcytics_sdk/testing/__init__.py +3 -0
  51. karcytics_sdk/testing/contract.py +72 -0
  52. karcytics_sdk-2.0.0.dist-info/METADATA +156 -0
  53. karcytics_sdk-2.0.0.dist-info/RECORD +56 -0
  54. karcytics_sdk-2.0.0.dist-info/WHEEL +5 -0
  55. karcytics_sdk-2.0.0.dist-info/entry_points.txt +2 -0
  56. karcytics_sdk-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ """Karcytics SDK — Software Development Kit for Karcytics desktop plugins.
2
+
3
+ Provides two main namespaces:
4
+ - plugin: Clean API for plugin development with zero unnecessary dependencies.
5
+ - host: Host-facing APIs for the Core application and SDK signing CLI.
6
+ """
7
+
8
+ from . import host, interfaces, plugin
9
+
10
+ __all__ = ["plugin", "host", "interfaces"]
File without changes
File without changes
@@ -0,0 +1,81 @@
1
+ def setup_diagnostics_parser(subparsers):
2
+ # Command: sbom
3
+ sbom_parser = subparsers.add_parser("sbom", help="Generates and prints the SBOM in the specified format.")
4
+ sbom_parser.add_argument(
5
+ "--format", type=str, choices=["--json", "--markdown"], default="--markdown", help="Output format"
6
+ )
7
+ sbom_parser.set_defaults(func=generate_sbom)
8
+
9
+ # Command: evaluate
10
+ eval_parser = subparsers.add_parser("evaluate", help="Perform comprehensive CLI evaluation of a plugin.")
11
+ eval_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
12
+ eval_parser.set_defaults(func=evaluate_plugin)
13
+
14
+ # Command: doctor
15
+ doctor_parser = subparsers.add_parser("doctor", help="Evaluate a plugin against new SDK architecture.")
16
+ doctor_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
17
+ doctor_parser.set_defaults(func=doctor_plugin)
18
+
19
+
20
+ def generate_sbom(args) -> bool:
21
+ """Generates and prints the SBOM in the specified format."""
22
+ try:
23
+ import importlib
24
+
25
+ karcytics_core_sbom = importlib.import_module("karcytics.core.sbom")
26
+ generator = karcytics_core_sbom.SBOMGenerator()
27
+
28
+ if args.format == "--json":
29
+ print(generator.to_json())
30
+ else:
31
+ print(generator.to_markdown())
32
+ return True
33
+ except ImportError:
34
+ print("ERROR: SBOM generation is only supported when running inside the Karcytics main application.")
35
+ return False
36
+
37
+
38
+ def evaluate_plugin(args) -> bool:
39
+ """Perform comprehensive CLI evaluation of a plugin."""
40
+ import logging
41
+ import sys
42
+ from pathlib import Path
43
+
44
+ from PyQt6.QtWidgets import QApplication
45
+
46
+ from karcytics_sdk.plugin.manifest_parser import ManifestParser
47
+
48
+ print("Running Plugin Diagnostics Evaluator (evaluate)...")
49
+ _app = QApplication.instance() or QApplication(sys.argv)
50
+ logging.basicConfig(level=logging.INFO)
51
+
52
+ try:
53
+ parser = ManifestParser()
54
+ manifest = parser.parse_file(str(Path(args.plugin_dir) / "pyproject.toml"))
55
+ print(f"Manifest parsed successfully: {manifest.get('id')} v{manifest.get('version')}")
56
+
57
+ dependencies = manifest.get("python_dependencies") or manifest.get("dependencies", {})
58
+ if dependencies:
59
+ print("Auditing Plugin Dependencies...")
60
+ all_pinned = True
61
+ for dep, version in dependencies.items():
62
+ if any(c in version for c in (">", "<", "*", "^", "~")):
63
+ print(f"WARNING: Dependency '{dep}' is not pinned. Recommend exact pinning.")
64
+ all_pinned = False
65
+ else:
66
+ print(f"Dependency '{dep}' is pinned to version '{version}'")
67
+
68
+ if all_pinned:
69
+ print("All declared dependencies are securely pinned.")
70
+
71
+ return True
72
+ except Exception as e:
73
+ print(f"Evaluation failed: {e}")
74
+ return False
75
+
76
+
77
+ def doctor_plugin(args) -> bool:
78
+ """Evaluate a plugin against new SDK architecture (plugin.toml, strict imports)."""
79
+ print(f"Running Plugin Conformance Evaluator (doctor) on {args.plugin_dir}...")
80
+ print("Note: Scaffolded for Phase 1. Full static/runtime checks to be implemented.")
81
+ return True
@@ -0,0 +1,112 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+
5
+ def setup_migrate_parser(subparsers):
6
+ # Command: migrate
7
+ migrate_parser = subparsers.add_parser("migrate", help="Migrate a legacy plugin to the new architecture.")
8
+ migrate_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
9
+ migrate_parser.set_defaults(func=migrate_plugin)
10
+
11
+
12
+ def migrate_plugin(args) -> bool: # noqa: C901
13
+ """Migrate a legacy plugin to the new architecture (pyproject.toml, src/ layout)."""
14
+ print(f"Migrating legacy plugin at {args.plugin_dir} to new SDK architecture...")
15
+
16
+ p_dir = Path(args.plugin_dir)
17
+ manifest_path = p_dir / "manifest.json"
18
+
19
+ if not manifest_path.exists():
20
+ print("ERROR: manifest.json not found. This doesn't look like a valid legacy plugin.")
21
+ return False
22
+
23
+ with open(manifest_path, encoding="utf-8") as f:
24
+ manifest_data = json.load(f)
25
+
26
+ plugin_id = manifest_data.get("id", p_dir.name.lower().replace("-", "_").replace(" ", "_"))
27
+
28
+ # 1. Generate pyproject.toml
29
+ print("1. Creating pyproject.toml and deprecating manifest.json...")
30
+
31
+ toml_path = p_dir / "pyproject.toml"
32
+
33
+ # Extract dependencies
34
+ dependencies_str = ""
35
+ if "python_dependencies" in manifest_data:
36
+ for dep, ver in manifest_data["python_dependencies"].items():
37
+ dependencies_str += f' "{dep}{ver}",\n'
38
+
39
+ # Build authors block for project
40
+ project_authors = ""
41
+ if "authors" in manifest_data:
42
+ for author in manifest_data["authors"]:
43
+ name = author.get("name", "Unknown")
44
+ project_authors += f' {{ name = "{name}" }},\n'
45
+
46
+ # Build authors block for plugin
47
+ plugin_authors = ""
48
+ if "authors" in manifest_data:
49
+ for author in manifest_data["authors"]:
50
+ name = author.get("name", "Unknown")
51
+ role = author.get("role", "Developer")
52
+ perms_str = json.dumps(author.get("permissions", []))
53
+ plugin_authors += f' {{ name = "{name}", role = "{role}", permissions = {perms_str} }},\n'
54
+
55
+ clean_project_authors = project_authors.rstrip(",\\n")
56
+ clean_deps = dependencies_str.rstrip(",\\n")
57
+ clean_plugin_authors = plugin_authors.rstrip(",\\n")
58
+ reqs_json = json.dumps(manifest_data.get("requires", ["task_scheduler", "logger", "event_bus"]))
59
+
60
+ toml_content = f'''[project]
61
+ name = "{manifest_data.get("name", p_dir.name.title())}"
62
+ version = "{manifest_data.get("version", "1.0.0")}"
63
+ description = "{manifest_data.get("description", "")}"
64
+ readme = "README.md"
65
+ requires-python = ">=3.11"
66
+ authors = [
67
+ {clean_project_authors}
68
+ ]
69
+ dependencies = [
70
+ {clean_deps}
71
+ ]
72
+
73
+ [tool.biopro.plugin]
74
+ id = "{plugin_id}"
75
+ min_core_version = "{manifest_data.get("min_core_version", "1.4.9")}"
76
+ entry_point = "biopro_plugins.{plugin_id}:initialize"
77
+ requires = {reqs_json}
78
+ authors = [
79
+ {clean_plugin_authors}
80
+ ]
81
+ '''
82
+ with open(toml_path, "w", encoding="utf-8") as f:
83
+ f.write(toml_content)
84
+
85
+ print(
86
+ " -> Renaming manifest.json to manifest.json.deprecated (DO NOT DELETE yet, the core still uses it internally!)"
87
+ )
88
+ manifest_path.rename(p_dir / "manifest.json.deprecated")
89
+
90
+ # 2. Setup src structure
91
+ print("2. Restructuring python files into src/...")
92
+ src_dir = p_dir / "src"
93
+ src_dir.mkdir(exist_ok=True)
94
+
95
+ plugin_pkg_dir = src_dir / "biopro_plugins" / plugin_id
96
+ plugin_pkg_dir.mkdir(parents=True, exist_ok=True)
97
+
98
+ # Move all .py files except setup.py to the new package directory
99
+ py_files_moved = 0
100
+ for file in p_dir.glob("*.py"):
101
+ if file.name not in {"setup.py", "conftest.py"}:
102
+ print(f" -> Moving {file.name} to {plugin_pkg_dir.relative_to(p_dir)}")
103
+ file.rename(plugin_pkg_dir / file.name)
104
+ py_files_moved += 1
105
+
106
+ if py_files_moved == 0:
107
+ # Create an empty __init__.py if no files were moved
108
+ (plugin_pkg_dir / "__init__.py").touch()
109
+
110
+ print("\nāœ… Migration complete! Please verify your imports in the new src/biopro_plugins/ folder.")
111
+ print("Run 'karcytics-sdk sign .' to sign the new structure.")
112
+ return True
@@ -0,0 +1,450 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def setup_scaffold_parser(subparsers):
5
+ # Command: create-manifest
6
+ manifest_parser = subparsers.add_parser("create-manifest", help="Bootstraps a fresh manifest.json for a plugin.")
7
+ manifest_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
8
+ manifest_parser.add_argument("--id", type=str, help="Custom plugin ID (snake_case).")
9
+ manifest_parser.add_argument("--name", type=str, help="Custom plugin display name.")
10
+ manifest_parser.add_argument("--version", type=str, help="Custom plugin version.")
11
+ manifest_parser.add_argument("--desc", type=str, help="Custom plugin description.")
12
+ manifest_parser.set_defaults(func=create_manifest)
13
+
14
+ # Command: bootstrap
15
+ bootstrap_parser = subparsers.add_parser("bootstrap", help="Create a complete boilerplate plugin skeleton.")
16
+ bootstrap_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
17
+ bootstrap_parser.set_defaults(func=bootstrap_plugin)
18
+
19
+ # Command: init
20
+ init_parser = subparsers.add_parser("init", help="Scaffold a fresh plugin repository.")
21
+ init_parser.add_argument("plugin_name", type=str, help="Name of the new plugin.")
22
+ init_parser.set_defaults(func=init_plugin)
23
+
24
+
25
+ def create_manifest(args) -> bool:
26
+ """Interactive/Scriptable bootstrapping for a pyproject.toml config."""
27
+ p_dir = Path(args.plugin_dir)
28
+ p_dir.mkdir(parents=True, exist_ok=True)
29
+ toml_path = p_dir / "pyproject.toml"
30
+
31
+ if toml_path.exists():
32
+ print(f"āš ļø pyproject.toml already exists at {toml_path}. Aborting to prevent overwrite.")
33
+ return False
34
+
35
+ # Gather inputs or default
36
+ p_id = args.id or p_dir.name.lower().replace("-", "_").replace(" ", "_")
37
+ p_name = args.name or p_dir.name.title()
38
+ p_version = args.version or "1.0.0"
39
+ p_description = args.desc or f"A high-performance Karcytics data plugin analyzing {p_name}."
40
+
41
+ toml_content = f'''[project]
42
+ name = "{p_name}"
43
+ version = "{p_version}"
44
+ description = "{p_description}"
45
+ readme = "README.md"
46
+ requires-python = ">=3.11"
47
+ authors = [
48
+ {{ name = "Developer Name" }}
49
+ ]
50
+ dependencies = [
51
+ ]
52
+
53
+ [project.optional-dependencies]
54
+ dev = [
55
+ "pytest",
56
+ "pytest-qt",
57
+ "ruff",
58
+ "mkdocs-material"
59
+ ]
60
+
61
+ [tool.biopro.plugin]
62
+ id = "{p_id}"
63
+ min_core_version = "1.4.9"
64
+ entry_point = "biopro_plugins.{p_id}:initialize"
65
+ requires = ["task_scheduler", "logger", "event_bus"]
66
+ authors = [
67
+ {{ name = "Developer Name", role = "Developer", permissions = ["read_workspace", "write_assets"] }}
68
+ ]
69
+ '''
70
+ with open(toml_path, "w", encoding="utf-8") as f:
71
+ f.write(toml_content)
72
+
73
+ print(f"šŸŽ‰ Successfully created plugin configuration at: {toml_path}")
74
+ return True
75
+
76
+
77
+ def bootstrap_plugin(args) -> bool:
78
+ """Create a complete boilerplate plugin skeleton with documentation and source template."""
79
+ p_dir = Path(args.plugin_dir)
80
+ p_dir.mkdir(parents=True, exist_ok=True)
81
+
82
+ # 1. Create subfolders
83
+ (p_dir / "src").mkdir(parents=True, exist_ok=True)
84
+ (p_dir / "docs").mkdir(parents=True, exist_ok=True)
85
+ workflows_dir = p_dir / ".github" / "workflows"
86
+ workflows_dir.mkdir(parents=True, exist_ok=True)
87
+
88
+ # 2. Create manifest.json (pyproject.toml)
89
+ import argparse
90
+
91
+ create_args = argparse.Namespace(plugin_dir=args.plugin_dir, id=None, name=None, version=None, desc=None)
92
+ create_manifest(create_args)
93
+
94
+ # 3. Create a clean __init__.py boilerplate implementing AnalysisBase
95
+ init_file = p_dir / "src" / "__init__.py"
96
+ init_content = """from karcytics_sdk.plugin import AnalysisBase
97
+
98
+ class CustomAnalysisPlugin(AnalysisBase):
99
+ \"\"\"Boilerplate analysis plugin demonstrating safe SDK interaction.\"\"\"
100
+
101
+ def execute(self, workspace_context):
102
+ \"\"\"Executes primary data processing workflow.
103
+
104
+ Args:
105
+ workspace_context: The host application environment and loaded data assets.
106
+ \"\"\"
107
+ self.logger.info("Executing custom boilerplate analysis workflow...")
108
+
109
+ # Access workspace variables
110
+ assets = workspace_context.get_assets()
111
+ self.logger.info(f"Loaded {len(assets)} raw assets in current workspace.")
112
+
113
+ # Complete work and publish progress
114
+ self.publish_progress(100, "Boilerplate execution completed.")
115
+ return {"status": "success", "processed_assets": len(assets)}
116
+ """
117
+ with open(init_file, "w", encoding="utf-8") as f:
118
+ f.write(init_content)
119
+
120
+ # 4. Create a README.md inside docs/
121
+ readme_file = p_dir / "docs" / "01_getting_started.md"
122
+ readme_content = f"""# Getting Started with {p_dir.name.title()}
123
+
124
+ Welcome to your freshly bootstrapped Karcytics plugin!
125
+
126
+ ## Architecture
127
+ This plugin is developed using the Karcytics-SDK. It exposes a single data analysis pipeline extending `AnalysisBase`.
128
+
129
+ ## Getting Started
130
+ 1. Edit `src/__init__.py` to implement your custom data algorithms.
131
+ 2. Maintain your documentation under the `docs/` folder for local integration with the Karcytics Help Center.
132
+ 3. Sign your plugin before loading using:
133
+ ```bash
134
+ karcytics-sdk sign .
135
+ ```
136
+ """
137
+ with open(readme_file, "w", encoding="utf-8") as f:
138
+ f.write(readme_content)
139
+
140
+ # 5. Create GitHub Actions workflows
141
+ ci_file = workflows_dir / "ci.yml"
142
+ ci_content = """name: CI — Tests & Lint
143
+
144
+ on:
145
+ push:
146
+ branches: [main, develop]
147
+ pull_request:
148
+
149
+ jobs:
150
+ test:
151
+ runs-on: ${{ matrix.os }}
152
+ strategy:
153
+ matrix:
154
+ os: [ubuntu-latest, macos-latest, windows-latest]
155
+ steps:
156
+ - uses: actions/checkout@v4
157
+ - name: Install uv
158
+ uses: astral-sh/setup-uv@v2
159
+ with:
160
+ version: "latest"
161
+ - name: Set up Python
162
+ uses: actions/setup-python@v5
163
+ with:
164
+ python-version: "3.11"
165
+ - name: Install system Qt deps (Ubuntu)
166
+ if: matrix.os == 'ubuntu-latest'
167
+ run: sudo apt-get update && sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-cursor0
168
+ - name: Install dependencies
169
+ run: |
170
+ uv sync
171
+ uv pip install git+https://github.com/KalaimaranB/Karcytics-SDK.git
172
+ shell: bash
173
+ - name: Run tests
174
+ run: uv run pytest tests/ -v
175
+ env:
176
+ QT_QPA_PLATFORM: offscreen
177
+ shell: bash
178
+
179
+ lint:
180
+ runs-on: ubuntu-latest
181
+ steps:
182
+ - uses: actions/checkout@v4
183
+ - name: Install uv
184
+ uses: astral-sh/setup-uv@v2
185
+ with:
186
+ version: "latest"
187
+ - name: Set up Python
188
+ uses: actions/setup-python@v5
189
+ with:
190
+ python-version: "3.11"
191
+ - run: uv pip install --system ruff
192
+ - run: ruff check src/ tests/
193
+ """
194
+ with open(ci_file, "w", encoding="utf-8") as f:
195
+ f.write(ci_content)
196
+
197
+ deploy_docs_file = workflows_dir / "deploy-docs.yml"
198
+ deploy_docs_content = """name: Deploy Documentation
199
+
200
+ on:
201
+ push:
202
+ branches:
203
+ - main
204
+ workflow_dispatch:
205
+
206
+ permissions:
207
+ contents: write
208
+
209
+ jobs:
210
+ deploy:
211
+ runs-on: ubuntu-latest
212
+ steps:
213
+ - uses: actions/checkout@v4
214
+ - name: Configure Git Credentials
215
+ run: |
216
+ git config user.name github-actions[bot]
217
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
218
+ - name: Install uv
219
+ uses: astral-sh/setup-uv@v2
220
+ with:
221
+ version: "latest"
222
+ - uses: actions/setup-python@v5
223
+ with:
224
+ python-version: "3.11"
225
+ - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
226
+ - uses: actions/cache@v4
227
+ with:
228
+ key: mkdocs-material-${{ env.cache_id }}
229
+ path: .cache
230
+ restore-keys: |
231
+ mkdocs-material-
232
+ - name: Install dependencies
233
+ run: |
234
+ uv pip install --system mkdocs-material "mkdocstrings[python]"
235
+ - name: Build and Deploy
236
+ env:
237
+ PYTHONPATH: src
238
+ run: mkdocs gh-deploy --force
239
+ """
240
+ with open(deploy_docs_file, "w", encoding="utf-8") as f:
241
+ f.write(deploy_docs_content)
242
+
243
+ release_file = workflows_dir / "release.yml"
244
+ release_content = """name: Auto-Release Plugin
245
+
246
+ on:
247
+ push:
248
+ branches:
249
+ - main
250
+
251
+ env:
252
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
253
+
254
+ jobs:
255
+ check_version:
256
+ name: Check Version Bump
257
+ runs-on: ubuntu-latest
258
+ outputs:
259
+ changed: ${{ steps.check.outputs.changed }}
260
+ version: ${{ steps.check.outputs.version }}
261
+ plugin_id: ${{ steps.check.outputs.plugin_id }}
262
+ release_notes: ${{ steps.check.outputs.release_notes }}
263
+ steps:
264
+ - name: Checkout Code
265
+ uses: actions/checkout@v4
266
+ - name: Set up Python
267
+ uses: actions/setup-python@v5
268
+ with:
269
+ python-version: "3.11"
270
+ - name: Check Version
271
+ id: check
272
+ run: |
273
+ python -c "
274
+ import sys
275
+ if sys.version_info >= (3, 11):
276
+ import tomllib as toml
277
+ else:
278
+ import toml
279
+
280
+ with open('pyproject.toml', 'rb') as f:
281
+ data = toml.load(f)
282
+
283
+ project = data.get('project', {})
284
+ plugin = data.get('tool', {}).get('biopro', {}).get('plugin', {})
285
+
286
+ plugin_id = plugin.get('id', '')
287
+ version = project.get('version', '')
288
+ release_notes = plugin.get('release_notes', 'No release notes provided.')
289
+
290
+ import os
291
+ import uuid
292
+
293
+ with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
294
+ f.write(f'plugin_id={plugin_id}\\n')
295
+ f.write(f'version={version}\\n')
296
+
297
+ eof_marker = str(uuid.uuid4())
298
+ f.write(f'release_notes<<{eof_marker}\\n')
299
+ f.write(f'{release_notes}\\n')
300
+ f.write(f'{eof_marker}\\n')
301
+ "
302
+ VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
303
+ TAG_NAME="v$VERSION"
304
+
305
+ if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
306
+ echo "Version $VERSION already released. Skipping build."
307
+ echo "changed=false" >> $GITHUB_OUTPUT
308
+ else
309
+ echo "New version $VERSION detected. Proceeding with release."
310
+ echo "changed=true" >> $GITHUB_OUTPUT
311
+ fi
312
+
313
+ release:
314
+ name: Evaluate, Sign & Release
315
+ needs: check_version
316
+ if: needs.check_version.outputs.changed == 'true'
317
+ runs-on: ubuntu-latest
318
+ permissions:
319
+ contents: write
320
+ steps:
321
+ - name: Checkout Code
322
+ uses: actions/checkout@v4
323
+ - name: Tag Repository
324
+ run: |
325
+ git config user.name "github-actions[bot]"
326
+ git config user.email "github-actions[bot]@users.noreply.github.com"
327
+ TAG_NAME="v${{ needs.check_version.outputs.version }}"
328
+ git tag $TAG_NAME
329
+ git push origin $TAG_NAME
330
+ - name: Install uv
331
+ uses: astral-sh/setup-uv@v2
332
+ with:
333
+ version: "latest"
334
+ - name: Set up Python
335
+ uses: actions/setup-python@v5
336
+ with:
337
+ python-version: "3.11"
338
+ - name: Install Qt System Dependencies
339
+ run: sudo apt-get update && sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-cursor0
340
+ - name: Install Karcytics SDK
341
+ run: |
342
+ uv pip install --system git+https://github.com/KalaimaranB/Karcytics-SDK.git
343
+ - name: SDK Evaluate Plugin
344
+ run: |
345
+ karcytics-sdk evaluate .
346
+ env:
347
+ QT_QPA_PLATFORM: offscreen
348
+ - name: Execute Project Signing
349
+ run: |
350
+ karcytics-sdk project-sign .
351
+ env:
352
+ KARCYTICS_PROJECT_PRIVATE_KEY: ${{ secrets.KARCYTICS_PROJECT_PRIVATE_KEY }}
353
+ QT_QPA_PLATFORM: offscreen
354
+ - name: Build Release ZIP
355
+ run: |
356
+ PLUGIN_ID="${{ needs.check_version.outputs.plugin_id }}"
357
+ NEW_VERSION="${{ needs.check_version.outputs.version }}"
358
+ ZIP_NAME="${PLUGIN_ID}_v${NEW_VERSION}.zip"
359
+ zip -r "$ZIP_NAME" . \\
360
+ --exclude "*.git*" \\
361
+ --exclude "*tests/*" \\
362
+ --exclude "*__pycache__/*" \\
363
+ --exclude "*.venv/*" \\
364
+ --exclude "*.plugin_venv/*" \\
365
+ --exclude "*.DS_Store" \\
366
+ --exclude "*.pem" \\
367
+ --exclude "*.key" \\
368
+ --exclude "*.pytest_cache/*" \\
369
+ --exclude "*.github/*"
370
+ - name: Create GitHub Release
371
+ uses: softprops/action-gh-release@v2
372
+ with:
373
+ tag_name: "v${{ needs.check_version.outputs.version }}"
374
+ name: "${{ needs.check_version.outputs.plugin_id }} v${{ needs.check_version.outputs.version }}"
375
+ body: |
376
+ ### Release Notes
377
+ ${{ needs.check_version.outputs.release_notes }}
378
+
379
+ ---
380
+ *Auto-generated changes below:*
381
+ generate_release_notes: true
382
+ files: "${{ needs.check_version.outputs.plugin_id }}_v${{ needs.check_version.outputs.version }}.zip"
383
+ - name: Checkout BioPro-Distribution
384
+ uses: actions/checkout@v4
385
+ with:
386
+ repository: KalaimaranB/BioPro-Distribution
387
+ token: ${{ secrets.DIST_PAT }}
388
+ path: dist-repo
389
+ - name: Update registry.json
390
+ run: |
391
+ cd dist-repo
392
+ python3 - <<'EOF2'
393
+ import json, sys
394
+ with open("registry.json") as f:
395
+ reg = json.load(f)
396
+ plugin_id = "${{ needs.check_version.outputs.plugin_id }}"
397
+ new_version = "${{ needs.check_version.outputs.version }}"
398
+ tag_name = f"v{new_version}"
399
+ zip_name = f"{plugin_id}_{tag_name}.zip"
400
+ download_url = f"https://github.com/${{ github.repository }}/releases/download/{tag_name}/{zip_name}"
401
+ if plugin_id in reg.get("plugins", {}):
402
+ reg["plugins"][plugin_id]["version"] = new_version
403
+ reg["plugins"][plugin_id]["download_url"] = download_url
404
+ with open("registry.json", "w") as f:
405
+ json.dump(reg, f, indent=2)
406
+ print(f"Updated {plugin_id} in registry.json")
407
+ else:
408
+ print(f"Error: {plugin_id} not found in registry.json")
409
+ sys.exit(1)
410
+ EOF2
411
+ - name: Open Registry Update PR
412
+ uses: peter-evans/create-pull-request@v6
413
+ with:
414
+ token: ${{ secrets.DIST_PAT }}
415
+ path: dist-repo
416
+ branch: "auto/update-${{ needs.check_version.outputs.plugin_id }}-v${{ needs.check_version.outputs.version }}"
417
+ title: "chore: bump ${{ needs.check_version.outputs.plugin_id }} to v${{ needs.check_version.outputs.version }}"
418
+ commit-message: "chore: bump ${{ needs.check_version.outputs.plugin_id }} to ${{ needs.check_version.outputs.version }}"
419
+ body: |
420
+ ## Automated Registry Update
421
+ Plugin **`${{ needs.check_version.outputs.plugin_id }}`** was released at tag `v${{ needs.check_version.outputs.version }}`.
422
+ ### Developer Release Notes
423
+ > ${{ needs.check_version.outputs.release_notes }}
424
+
425
+ This PR updates `registry.json` with the latest version.
426
+ > āš ļø **Review before merging:** Verify the download URL is correct and the ZIP is valid.
427
+ add-paths: registry.json
428
+ """
429
+ with open(release_file, "w", encoding="utf-8") as f:
430
+ f.write(release_content)
431
+
432
+ print(f"\nšŸš€ Successfully bootstrapped boilerplate plugin at: {p_dir}")
433
+ print("Structure Created:")
434
+ print(" ā”œā”€ā”€ pyproject.toml (Configuration)")
435
+ print(" ā”œā”€ā”€ src/")
436
+ print(" │ └── __init__.py (Core plugin logic)")
437
+ print(" ā”œā”€ā”€ docs/")
438
+ print(" │ └── 01_getting_started.md (Documentation)")
439
+ print(" └── .github/workflows/")
440
+ print(" └── ci.yml, deploy-docs.yml, release.yml")
441
+ print("\nGet started by running:")
442
+ print(f' cd "{p_dir}" && karcytics-sdk init-identity && karcytics-sdk sign .')
443
+ return True
444
+
445
+
446
+ def init_plugin(args) -> bool:
447
+ """Scaffold a fresh plugin repository with src/ layout and plugin.toml."""
448
+ print(f"Scaffolding new plugin '{args.plugin_name}' with karcytics-sdk init...")
449
+ print("Note: Scaffolded for Phase 1.")
450
+ return True