spaceforge 0.0.2__py3-none-any.whl → 0.0.3__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.
@@ -0,0 +1,595 @@
1
+ Metadata-Version: 2.4
2
+ Name: spaceforge
3
+ Version: 0.0.3
4
+ Summary: A Python framework for building Spacelift plugins
5
+ Home-page: https://github.com/spacelift-io/plugins
6
+ Author: Spacelift
7
+ Author-email: Spacelift <support@spacelift.io>
8
+ Maintainer-email: Spacelift <support@spacelift.io>
9
+ License: MIT
10
+ Project-URL: Homepage, https://github.com/spacelift-io/plugins
11
+ Project-URL: Documentation, https://github.com/spacelift-io/plugins#readme
12
+ Project-URL: Repository, https://github.com/spacelift-io/plugins
13
+ Project-URL: Bug Reports, https://github.com/spacelift-io/plugins/issues
14
+ Keywords: spacelift,plugin,framework,infrastructure,devops,spaceforge
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: PyYAML>=6.0
29
+ Requires-Dist: click>=8.0.0
30
+ Requires-Dist: pydantic>=2.11.7
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=6.0; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: black; extra == "dev"
35
+ Requires-Dist: isort; extra == "dev"
36
+ Requires-Dist: mypy; extra == "dev"
37
+ Requires-Dist: types-PyYAML; extra == "dev"
38
+ Requires-Dist: setuptools-scm[toml]>=6.2; extra == "dev"
39
+ Requires-Dist: autoflake; extra == "dev"
40
+ Dynamic: author
41
+ Dynamic: home-page
42
+ Dynamic: license-file
43
+ Dynamic: requires-python
44
+
45
+ # Spaceforge - Build Spacelift Plugins in Python
46
+
47
+ Spaceforge is a Python framework that makes it easy to build powerful Spacelift plugins using a declarative, hook-based approach. Define your plugin logic in Python, and spaceforge automatically generates the plugin manifest for Spacelift.
48
+
49
+ ## Installation
50
+
51
+ Install spaceforge from PyPI:
52
+
53
+ ```bash
54
+ pip install spaceforge
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Create Your Plugin
60
+
61
+ Create a Python file (e.g., `my_plugin.py`) and inherit from `SpaceforgePlugin`:
62
+
63
+ ```python
64
+ from spaceforge import SpaceforgePlugin, Parameter, Variable, Context
65
+ import os
66
+
67
+ class MyPlugin(SpaceforgePlugin):
68
+ # Plugin metadata
69
+ __plugin_name__ = "my-awesome-plugin"
70
+ __version__ = "1.0.0"
71
+ __author__ = "Your Name"
72
+
73
+ # Define plugin parameters
74
+ __parameters__ = [
75
+ Parameter(
76
+ name="api_key",
77
+ description="API key for external service",
78
+ required=True,
79
+ sensitive=True
80
+ ),
81
+ Parameter(
82
+ name="environment",
83
+ description="Target environment",
84
+ required=False,
85
+ default="production"
86
+ )
87
+ ]
88
+
89
+ # Define Spacelift contexts
90
+ __contexts__ = [
91
+ Context(
92
+ name_prefix="my-plugin",
93
+ description="Main plugin context",
94
+ env=[
95
+ Variable(
96
+ key="API_KEY",
97
+ value_from_parameter="api_key",
98
+ sensitive=True
99
+ ),
100
+ Variable(
101
+ key="ENVIRONMENT",
102
+ value_from_parameter="environment"
103
+ )
104
+ ]
105
+ )
106
+ ]
107
+
108
+ def after_plan(self):
109
+ """Run security checks after Terraform plan"""
110
+ # Run external commands
111
+ return_code, stdout, stderr = self.run_cli("my-security-tool", "--scan", "./", '--api', os.environ["API_KEY"])
112
+
113
+ if return_code != 0:
114
+ self.logger.error("Security scan failed!")
115
+ exit(1)
116
+
117
+ self.logger.info("Security scan passed!")
118
+ ```
119
+
120
+ ### 2. Generate Plugin Manifest
121
+
122
+ Generate the Spacelift plugin YAML manifest:
123
+
124
+ ```bash
125
+ spaceforge generate my_plugin.py
126
+ ```
127
+
128
+ This creates `plugin.yaml` that you can upload to Spacelift.
129
+
130
+ ### 3. Test Your Plugin
131
+
132
+ Test individual hooks locally:
133
+
134
+ ```bash
135
+ # Set parameter values
136
+ export API_KEY="your-api-key"
137
+ export ENVIRONMENT="staging"
138
+
139
+ # Test the after_plan hook
140
+ spaceforge runner after_plan
141
+ ```
142
+
143
+ ## Available Hooks
144
+
145
+ Override these methods in your plugin to add custom logic:
146
+
147
+ - `before_init()` - Before Terraform init
148
+ - `after_init()` - After Terraform init
149
+ - `before_plan()` - Before Terraform plan
150
+ - `after_plan()` - After Terraform plan
151
+ - `before_apply()` - Before Terraform apply
152
+ - `after_apply()` - After Terraform apply
153
+ - `before_perform()` - Before the run performs
154
+ - `after_perform()` - After the run performs
155
+ - `before_destroy()` - Before Terraform destroy
156
+ - `after_destroy()` - After Terraform destroy
157
+ - `after_run()` - After the run completes
158
+
159
+ ## Plugin Components
160
+
161
+ ### Parameters
162
+
163
+ Define user-configurable parameters:
164
+
165
+ ```python
166
+ __parameters__ = [
167
+ Parameter(
168
+ name="database_url",
169
+ description="Database connection URL",
170
+ required=True,
171
+ sensitive=True,
172
+ default="postgresql://localhost:5432/mydb"
173
+ ),
174
+ Parameter(
175
+ name="timeout",
176
+ description="Timeout in seconds",
177
+ required=False,
178
+ default=30
179
+ )
180
+ ]
181
+ ```
182
+
183
+ ### Contexts
184
+
185
+ Define Spacelift contexts with environment variables and custom hooks:
186
+
187
+ ```python
188
+ __contexts__ = [
189
+ Context(
190
+ name_prefix="production",
191
+ description="Production environment context",
192
+ labels={"env": "prod"},
193
+ env=[
194
+ Variable(
195
+ key="DATABASE_URL",
196
+ value_from_parameter="database_url",
197
+ sensitive=True
198
+ ),
199
+ Variable(
200
+ key="API_ENDPOINT",
201
+ value="https://api.prod.example.com"
202
+ )
203
+ ],
204
+ hooks={
205
+ "before_apply": [
206
+ "echo 'Starting production deployment'",
207
+ "kubectl get pods"
208
+ ]
209
+ }
210
+ )
211
+ ]
212
+ ```
213
+
214
+ ### Binaries
215
+
216
+ Automatically download and install external tools:
217
+
218
+ ```python
219
+ __binaries__ = [
220
+ Binary(
221
+ name="kubectl",
222
+ download_urls={
223
+ "amd64": "https://dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl",
224
+ "arm64": "https://dl.k8s.io/release/v1.28.0/bin/linux/arm64/kubectl"
225
+ }
226
+ )
227
+ ]
228
+ ```
229
+
230
+ ### Policies
231
+
232
+ Define OPA policies for your plugin:
233
+
234
+ ```python
235
+ __policies__ = [
236
+ Policy(
237
+ name_prefix="security-check",
238
+ type="notification",
239
+ body="""
240
+ package spacelift
241
+
242
+ webhook[{"endpoint_id": "security-alerts"}] {
243
+ input.run_updated.run.marked_unsafe == true
244
+ }
245
+ """,
246
+ labels={"type": "security"}
247
+ )
248
+ ]
249
+ ```
250
+
251
+ ### Webhooks
252
+
253
+ Define webhooks to trigger external actions:
254
+
255
+ ```python
256
+ __webhooks__ = [
257
+ Webhook(
258
+ name_prefix="security-alerts",
259
+ description="Send security alerts to external service",
260
+ endpoint="https://alerts.example.com/webhook",
261
+ secrets=[
262
+ Variable(key="amazing", value="secret-value", sensitive=True)
263
+ ],
264
+ )
265
+ ]
266
+ ```
267
+
268
+ ## Plugin Features
269
+
270
+ ### Logging
271
+
272
+ Built-in structured logging with run context:
273
+
274
+ ```python
275
+ def after_plan(self):
276
+ self.logger.info("Starting security scan")
277
+ self.logger.debug("Debug info (only shown when SPACELIFT_DEBUG=true)")
278
+ self.logger.warning("Warning message")
279
+ self.logger.error("Error occurred")
280
+ ```
281
+
282
+ ### CLI Execution
283
+
284
+ Run external commands with automatic logging:
285
+
286
+ ```python
287
+ def before_apply(self):
288
+ # Run command with automatic output capture
289
+ return_code, stdout, stderr = self.run_cli("terraform", "validate")
290
+
291
+ if return_code != 0:
292
+ self.logger.error("Terraform validation failed")
293
+ exit(1)
294
+ ```
295
+
296
+ ### Spacelift API Integration
297
+
298
+ Query the Spacelift GraphQL API (requires `SPACELIFT_API_TOKEN` and `SPACELIFT_DOMAIN`):
299
+
300
+ ```python
301
+ def after_plan(self):
302
+ result = self.query_api("""
303
+ query {
304
+ stack(id: "my-stack-id") {
305
+ name
306
+ state
307
+ latestRun {
308
+ id
309
+ state
310
+ }
311
+ }
312
+ }
313
+ """)
314
+
315
+ self.logger.info(f"Stack state: {result['stack']['state']}")
316
+ ```
317
+
318
+ ### Access Plan and State
319
+
320
+ Access Terraform plan and state data:
321
+
322
+ ```python
323
+ def after_plan(self):
324
+ # Get the current plan
325
+ plan = self.get_plan_json()
326
+
327
+ # Get the state before changes
328
+ state = self.get_state_before_json()
329
+
330
+ # Analyze planned changes
331
+ resource_count = len(plan.get('planned_values', {}).get('root_module', {}).get('resources', []))
332
+ self.logger.info(f"Planning to manage {resource_count} resources")
333
+ ```
334
+
335
+ ### Send Rich Output
336
+
337
+ Send formatted markdown to the Spacelift UI:
338
+
339
+ ```python
340
+ def after_plan(self):
341
+ markdown = """
342
+ # Security Scan Results
343
+
344
+ ✅ **Passed:** 45 checks
345
+ ⚠️ **Warnings:** 3 issues
346
+ ❌ **Failed:** 0 critical issues
347
+
348
+ [View detailed report](https://security.example.com/reports/123)
349
+ """
350
+
351
+ self.send_markdown(markdown)
352
+ ```
353
+
354
+ ### Append to Policy Input
355
+
356
+ Append custom data to the OPA policy input:
357
+
358
+ The following example will create input available via `input.third_party_metadata.custom.my_custom_data` in your OPA policies:
359
+ ```python
360
+ def after_plan(self):
361
+ self.append_policy_input("my_custom_data", {
362
+ "scan_results": {
363
+ "passed": True,
364
+ "issues": []
365
+ }
366
+ })
367
+ ```
368
+
369
+ ## CLI Commands
370
+
371
+ ### Generate Plugin Manifest
372
+
373
+ ```bash
374
+ # Generate from plugin.py (default filename)
375
+ spaceforge generate
376
+
377
+ # Generate from specific file
378
+ spaceforge generate my_plugin.py
379
+
380
+ # Specify output file
381
+ spaceforge generate my_plugin.py -o custom-output.yaml
382
+
383
+ # Get help
384
+ spaceforge generate --help
385
+ ```
386
+
387
+ ### Test Plugin Hooks
388
+
389
+ ```bash
390
+ # Set parameters via environment variables
391
+ export SPACEFORGE_PARAM_API_KEY="test-key"
392
+ export SPACEFORGE_PARAM_TIMEOUT="60"
393
+
394
+ # Test specific hook
395
+ spaceforge runner after_plan
396
+
397
+ # Test with specific plugin file
398
+ spaceforge runner --plugin-file my_plugin.py before_apply
399
+
400
+ # Get help
401
+ spaceforge runner --help
402
+ ```
403
+
404
+ ## Plugin Development Tips
405
+
406
+ ### 1. Handle Dependencies
407
+
408
+ If your plugin needs Python packages, create a `requirements.txt` file. Spaceforge automatically adds a `before_init` hook to install them:
409
+
410
+ ```txt
411
+ requests>=2.28.0
412
+ pydantic>=1.10.0
413
+ ```
414
+
415
+ ### 2. Environment Variables
416
+
417
+ Access Spacelift environment variables in your hooks:
418
+
419
+ ```python
420
+ def after_plan(self):
421
+ run_id = os.environ.get('SPACELIFT_RUN_ID')
422
+ stack_id = os.environ.get('SPACELIFT_STACK_ID')
423
+ self.logger.info(f"Processing run {run_id} for stack {stack_id}")
424
+ ```
425
+
426
+ ### 3. Error Handling
427
+
428
+ Always handle errors gracefully:
429
+
430
+ ```python
431
+ def after_plan(self):
432
+ try:
433
+ # Your plugin logic here
434
+ result = self.run_external_service()
435
+
436
+ except Exception as e:
437
+ self.logger.error(f"Plugin failed: {str(e)}")
438
+ # Exit with non-zero code to fail the run
439
+ exit(1)
440
+ ```
441
+
442
+ ### 4. Testing and Debugging
443
+
444
+ - Set `SPACELIFT_DEBUG=true` to enable debug logging
445
+ - Use the runner command to test hooks during development
446
+ - Test with different parameter combinations
447
+ - Validate your generated YAML before uploading to Spacelift
448
+
449
+ ## Example: Security Scanning Plugin
450
+
451
+ Here's a complete example of a security scanning plugin:
452
+
453
+ ```python
454
+ import os
455
+ import json
456
+ from spaceforge import SpaceforgePlugin, Parameter, Variable, Context, Binary
457
+
458
+ class SecurityScannerPlugin(SpaceforgePlugin):
459
+ __plugin_name__ = "security-scanner"
460
+ __version__ = "1.0.0"
461
+ __author__ = "Security Team"
462
+
463
+ __binaries__ = [
464
+ Binary(
465
+ name="security-cli",
466
+ download_urls={
467
+ "amd64": "https://releases.example.com/security-cli-linux-amd64",
468
+ "arm64": "https://releases.example.com/security-cli-linux-arm64"
469
+ }
470
+ )
471
+ ]
472
+
473
+ __parameters__ = [
474
+ Parameter(
475
+ name="api_token",
476
+ description="Security service API token",
477
+ required=True,
478
+ sensitive=True
479
+ ),
480
+ Parameter(
481
+ name="severity_threshold",
482
+ description="Minimum severity level to report",
483
+ required=False,
484
+ default="medium"
485
+ )
486
+ ]
487
+
488
+ __contexts__ = [
489
+ Context(
490
+ name_prefix="security-scanner",
491
+ description="Security scanning context",
492
+ env=[
493
+ Variable(
494
+ key="SECURITY_API_TOKEN",
495
+ value_from_parameter="api_token",
496
+ sensitive=True
497
+ ),
498
+ Variable(
499
+ key="SEVERITY_THRESHOLD",
500
+ value_from_parameter="severity_threshold"
501
+ )
502
+ ]
503
+ )
504
+ ]
505
+
506
+ def after_plan(self):
507
+ """Run security scan after Terraform plan"""
508
+ self.logger.info("Starting security scan of Terraform plan")
509
+
510
+ # Authenticate with security service
511
+ return_code, stdout, stderr = self.run_cli(
512
+ "security-cli", "auth",
513
+ "--token", os.environ["SECURITY_API_TOKEN"]
514
+ )
515
+
516
+ if return_code != 0:
517
+ self.logger.error("Failed to authenticate with security service")
518
+ exit(1)
519
+
520
+ # Scan the Terraform plan
521
+ return_code, stdout, stderr = self.run_cli(
522
+ "security-cli", "scan", "terraform",
523
+ "--plan-file", "spacelift.plan.json",
524
+ "--format", "json",
525
+ "--severity", os.environ.get("SEVERITY_THRESHOLD", "medium"),
526
+ print_output=False
527
+ )
528
+
529
+ if return_code != 0:
530
+ self.logger.error("Security scan failed")
531
+ for line in stderr:
532
+ self.logger.error(line)
533
+ exit(1)
534
+
535
+ # Parse scan results
536
+ try:
537
+ results = json.loads('\n'.join(stdout))
538
+
539
+ # Generate markdown report
540
+ markdown = self._generate_report(results)
541
+ self.send_markdown(markdown)
542
+
543
+ # Fail run if critical issues found
544
+ if results.get('critical_count', 0) > 0:
545
+ self.logger.error(f"Found {results['critical_count']} critical security issues")
546
+ exit(1)
547
+
548
+ self.logger.info("Security scan completed successfully")
549
+
550
+ except json.JSONDecodeError:
551
+ self.logger.error("Failed to parse scan results")
552
+ exit(1)
553
+
554
+ def _generate_report(self, results):
555
+ """Generate markdown report from scan results"""
556
+ report = "# Security Scan Results\n\n"
557
+
558
+ if results.get('total_issues', 0) == 0:
559
+ report += "✅ **No security issues found!**\n"
560
+ else:
561
+ report += f"Found {results['total_issues']} security issues:\n\n"
562
+
563
+ for severity in ['critical', 'high', 'medium', 'low']:
564
+ count = results.get(f'{severity}_count', 0)
565
+ if count > 0:
566
+ emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🟢'}[severity]
567
+ report += f"- {emoji} **{severity.upper()}:** {count}\n"
568
+
569
+ if results.get('report_url'):
570
+ report += f"\n[View detailed report]({results['report_url']})\n"
571
+
572
+ return report
573
+ ```
574
+
575
+ Generate and test this plugin:
576
+
577
+ ```bash
578
+ # Generate the manifest
579
+ spaceforge generate security_scanner.py
580
+
581
+ # Test locally
582
+ export SPACEFORGE_PARAM_API_TOKEN="your-token"
583
+ export SPACEFORGE_PARAM_SEVERITY_THRESHOLD="high"
584
+ spaceforge runner after_plan
585
+ ```
586
+
587
+ ## Next Steps
588
+
589
+ 1. **Install spaceforge:** `pip install spaceforge`
590
+ 2. **Create your plugin:** Start with the quick start example
591
+ 3. **Test locally:** Use the runner command to test your hooks
592
+ 4. **Generate manifest:** Use the generate command to create plugin.yaml
593
+ 5. **Upload to Spacelift:** Add your plugin manifest to your Spacelift account
594
+
595
+ For more advanced examples, see the [plugins](plugins/) directory in this repository.
@@ -0,0 +1,31 @@
1
+ spaceforge/README.md,sha256=8o1Nuyasb4OxX3E7ZycyducOrR4J19bZcHrLvFeoFNg,7730
2
+ spaceforge/__init__.py,sha256=TU-vvm15dK1ucixNW0V42eTT72x3_hmKSyxP4MC1Occ,589
3
+ spaceforge/__main__.py,sha256=c3nAw4WBnHXIcfMlRV6Ja7r87pEhSeK-SAqiSYIasIY,643
4
+ spaceforge/_version.py,sha256=RP_LfUd4ODnrfwn9nam8wB6bR3lM4VwmoRxK08Tkiiw,2155
5
+ spaceforge/_version_scm.py,sha256=pBZsQt6tlL02W-ri--X_4JCubpAK7jjCSnOmUp_isjc,704
6
+ spaceforge/cls.py,sha256=pjJ4vq_1oJLP8Rv_8WFWZ6JOtka--53lfYq5IQzcu_g,6007
7
+ spaceforge/conftest.py,sha256=U-xCavCsgRAQXqflIIOMeq9pcGbeqRviUNkEXgZol8g,2141
8
+ spaceforge/generator.py,sha256=t1i2SgBlO0hr5wnDF2ki4aOAdPPv7WCCGsX1PH8P4Cc,16267
9
+ spaceforge/plugin.py,sha256=RVrAbwjnyyMlExX25GBmuf32F-0DYPcUR9w8dCPXjPE,13031
10
+ spaceforge/runner.py,sha256=aBQQLG8MFVrqCzM0X8HgERYbhLKbmlOHUPKjV7-xvpA,3163
11
+ spaceforge/schema.json,sha256=05_eogpaP1PelNnT0tfh0ZPdMUFQFYaFPSzHLQICfqw,10521
12
+ spaceforge/test_cls.py,sha256=nXAgbnFnGdFxrtA7vNXiePjNUASuoYW-lEuQGx9WMGs,468
13
+ spaceforge/test_generator.py,sha256=j9z2fQpHVA9kdBEcS2t03HjqQAEuJELVMJ07ZQP0Pd0,28820
14
+ spaceforge/test_generator_binaries.py,sha256=3ceeOAS1fkVc7bVcvSgGGqIur-Z7gK4l5RpHMmilu2o,6189
15
+ spaceforge/test_generator_core.py,sha256=gOqRx0rnME-srGMHun4KidXMN-iaqfKKTyoQ0Tw6b9Q,6253
16
+ spaceforge/test_generator_hooks.py,sha256=2lJs8dYlFb7QehWcYF0O4qg38s5UudEpzJyBi1XiS3k,2542
17
+ spaceforge/test_generator_parameters.py,sha256=77az9rcocFny2AC4O2eTzjCW712fR1DBHzGrgBKeR4w,1878
18
+ spaceforge/test_plugin.py,sha256=pFst9RUpTujuv_zdhWnB9LMeB_vN9qHLNEhnmo8WzkQ,13046
19
+ spaceforge/test_plugin_file_operations.py,sha256=B0qvIo5EcfKMiHLhBv-hAnpSonn83ojcmJHXasydojA,3782
20
+ spaceforge/test_plugin_hooks.py,sha256=rNCZZyd_SDMkm1x3yl5mjQ5tBMGm3YNd1U6h_niWRQs,2962
21
+ spaceforge/test_plugin_inheritance.py,sha256=WHfvU5s-2GtfcI9-1bHXH7bacr77ikq68V3Z3BBQKvQ,3617
22
+ spaceforge/test_runner.py,sha256=HOGJ-RAA1k82zLWPgtYVWseh7KMXJKGs2S2xDf2TgJo,17730
23
+ spaceforge/test_runner_cli.py,sha256=Sf5X0O9Wc9EhGB5L8SzvlmO7QmgQZQoClSdNYefa-lQ,2299
24
+ spaceforge/test_runner_core.py,sha256=eNR9YOwJwv7LsMtNQ4WXXMPIW6RE_A7hUp4bCpzz1Rk,3941
25
+ spaceforge/test_runner_execution.py,sha256=BRlOApCmlpjE9YYvqHc8creSRu2vyPubzOCVXv-on0w,5335
26
+ spaceforge-0.0.3.dist-info/licenses/LICENSE,sha256=wyljRrfnWY2ggQKkSCg3Nw2hxwPMmupopaKs9Kpgys8,1065
27
+ spaceforge-0.0.3.dist-info/METADATA,sha256=Sas3GTwcjfYPzJ5_pOv08KQH3jGC-R8RJYzTc87o2xM,15991
28
+ spaceforge-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ spaceforge-0.0.3.dist-info/entry_points.txt,sha256=qawuuKBSNTGg-njnQnhxxFldFvXYAPej6bF_f3iyQ48,56
30
+ spaceforge-0.0.3.dist-info/top_level.txt,sha256=eVw-Lw4Th0oHM8Gx1Y8YetyNgbNbMBU00yWs-kwGeSs,11
31
+ spaceforge-0.0.3.dist-info/RECORD,,