swiftapi-python 1.2.1__py3-none-any.whl → 1.3.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.
swiftapi/verifier.py CHANGED
@@ -1,130 +1,182 @@
1
- """
2
- SwiftAPI SDK - Ed25519 Signature Verifier (The Shield)
3
-
4
- This module provides OFFLINE cryptographic verification of execution attestations.
5
- It does not require network connectivity - it uses the hardcoded SwiftAPI public key.
6
- """
7
-
8
- import base64
9
- import json
10
- from datetime import datetime, timezone
11
- from typing import Dict, Any
12
-
13
- from nacl.signing import VerifyKey
14
- from nacl.exceptions import BadSignatureError
15
-
16
- from .exceptions import (
17
- SignatureVerificationError,
18
- AttestationExpiredError,
19
- )
20
-
21
- # SwiftAPI's Ed25519 Public Key (Base64)
22
- # This is the ONLY source of truth for attestation verification
23
- SWIFTAPI_PUBLIC_KEY_B64 = "jajZXZ0R3CUWkE5/5Mxx5Y76CdsSzaPDuT7aWnooZSk="
24
-
25
- # Decode public key once at module load
26
- SWIFTAPI_PUBLIC_KEY = base64.b64decode(SWIFTAPI_PUBLIC_KEY_B64)
27
- VERIFY_KEY = VerifyKey(SWIFTAPI_PUBLIC_KEY)
28
-
29
-
30
- def verify_signature(attestation: Dict[str, Any], ignore_expiry: bool = False) -> bool:
31
- """
32
- Verify the Ed25519 signature of an execution attestation.
33
-
34
- This is OFFLINE verification - no network required.
35
- The signature proves the attestation was issued by SwiftAPI.
36
-
37
- Signature authenticity is permanent. Expiry is about the execution window,
38
- not whether the attestation is genuine. Use ignore_expiry=True for audit
39
- and post-hoc verification.
40
-
41
- Args:
42
- attestation: The execution_attestation dict from /verify response
43
- ignore_expiry: If True, verify signature without checking expiry
44
-
45
- Returns:
46
- True if signature is valid
47
-
48
- Raises:
49
- SignatureVerificationError: If signature is invalid or missing
50
- AttestationExpiredError: If attestation has expired (and ignore_expiry is False)
51
- """
52
- # Extract required fields
53
- signature_b64 = attestation.get("signature")
54
- if not signature_b64:
55
- raise SignatureVerificationError("Missing signature in attestation")
56
-
57
- jti = attestation.get("jti")
58
- action_fingerprint = attestation.get("action_fingerprint")
59
- expires_at = attestation.get("expires_at")
60
-
61
- if not all([jti, action_fingerprint, expires_at]):
62
- raise SignatureVerificationError("Incomplete attestation: missing required fields")
63
-
64
- # Verify signature FIRST — authenticity is permanent
65
- signed_payload = _reconstruct_signed_payload(attestation)
66
-
67
- try:
68
- signature = base64.b64decode(signature_b64)
69
- except Exception as e:
70
- raise SignatureVerificationError(f"Invalid signature encoding: {e}")
71
-
72
- try:
73
- VERIFY_KEY.verify(signed_payload, signature)
74
- except BadSignatureError:
75
- raise SignatureVerificationError(
76
- "INVALID SIGNATURE: Attestation was not signed by SwiftAPI. "
77
- "This could indicate a forged or tampered attestation."
78
- )
79
-
80
- # Check expiration AFTER signature (execution window, not authenticity)
81
- if not ignore_expiry:
82
- try:
83
- expiry = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
84
- now = datetime.now(timezone.utc)
85
- if now > expiry:
86
- raise AttestationExpiredError(f"Attestation expired at {expires_at}")
87
- except ValueError as e:
88
- raise SignatureVerificationError(f"Invalid expiration format: {e}")
89
-
90
- return True
91
-
92
-
93
- def _reconstruct_signed_payload(attestation: Dict[str, Any]) -> bytes:
94
- """
95
- Reconstruct the exact payload that was signed by SwiftAPI.
96
-
97
- The server signs the attestation BEFORE adding signature/signing_mode fields.
98
- We must reconstruct that exact payload.
99
- """
100
- # Copy attestation and remove fields that weren't in the signed payload.
101
- # signature/signing_mode are added after signing.
102
- # action_data/denial_reason are added by /attest after signing.
103
- _unsigned = ("signature", "signing_mode", "action_data", "denial_reason")
104
- signed_fields = {k: v for k, v in attestation.items() if k not in _unsigned}
105
-
106
- # Deterministic JSON serialization (must match server exactly)
107
- payload_str = json.dumps(signed_fields, sort_keys=True, separators=(",", ":"))
108
- return payload_str.encode("utf-8")
109
-
110
-
111
- def get_public_key() -> str:
112
- """Return SwiftAPI's public key in Base64 format."""
113
- return SWIFTAPI_PUBLIC_KEY_B64
114
-
115
-
116
- def is_valid(attestation: Dict[str, Any], ignore_expiry: bool = False) -> bool:
117
- """
118
- Check if attestation is valid without raising exceptions.
119
-
120
- Args:
121
- attestation: The execution_attestation dict
122
- ignore_expiry: If True, only check signature authenticity
123
-
124
- Returns:
125
- True if valid, False otherwise
126
- """
127
- try:
128
- return verify_signature(attestation, ignore_expiry=ignore_expiry)
129
- except Exception:
130
- return False
1
+ """
2
+ SwiftAPI SDK - Ed25519 Signature Verifier (The Shield)
3
+
4
+ This module provides OFFLINE cryptographic verification of execution attestations.
5
+ It does not require network connectivity - it uses the hardcoded SwiftAPI public key.
6
+ """
7
+
8
+ import base64
9
+ import hashlib
10
+ import json
11
+ from datetime import datetime, timezone
12
+ from typing import Dict, Any
13
+
14
+ from nacl.signing import VerifyKey
15
+ from nacl.exceptions import BadSignatureError
16
+
17
+ from .exceptions import (
18
+ SignatureVerificationError,
19
+ AttestationExpiredError,
20
+ SwiftAPIError,
21
+ )
22
+
23
+ # SwiftAPI's Ed25519 Public Key (Base64)
24
+ # This is the ONLY source of truth for attestation verification
25
+ SWIFTAPI_PUBLIC_KEY_B64 = "jajZXZ0R3CUWkE5/5Mxx5Y76CdsSzaPDuT7aWnooZSk="
26
+
27
+ # Decode public key once at module load
28
+ SWIFTAPI_PUBLIC_KEY = base64.b64decode(SWIFTAPI_PUBLIC_KEY_B64)
29
+ VERIFY_KEY = VerifyKey(SWIFTAPI_PUBLIC_KEY)
30
+
31
+
32
+ def verify_signature(attestation: Dict[str, Any], ignore_expiry: bool = False) -> bool:
33
+ """
34
+ Verify the Ed25519 signature of an execution attestation.
35
+
36
+ This is OFFLINE verification - no network required.
37
+ The signature proves the attestation was issued by SwiftAPI.
38
+
39
+ Signature authenticity is permanent. Expiry is about the execution window,
40
+ not whether the attestation is genuine. Use ignore_expiry=True for audit
41
+ and post-hoc verification.
42
+
43
+ Args:
44
+ attestation: The execution_attestation dict from /verify response
45
+ ignore_expiry: If True, verify signature without checking expiry
46
+
47
+ Returns:
48
+ True if signature is valid
49
+
50
+ Raises:
51
+ SignatureVerificationError: If signature is invalid or missing
52
+ AttestationExpiredError: If attestation has expired (and ignore_expiry is False)
53
+ """
54
+ if not isinstance(attestation, dict):
55
+ raise SignatureVerificationError("Attestation must be a JSON object")
56
+
57
+ # Extract required fields
58
+ signature_b64 = attestation.get("signature")
59
+ if not signature_b64:
60
+ raise SignatureVerificationError("Missing signature in attestation")
61
+
62
+ jti = attestation.get("jti")
63
+ action_fingerprint = attestation.get("action_fingerprint")
64
+ expires_at = attestation.get("expires_at")
65
+
66
+ if not all(isinstance(value, str) and value for value in (jti, action_fingerprint, expires_at)):
67
+ raise SignatureVerificationError("Incomplete attestation: missing required fields")
68
+
69
+ # Verify signature FIRST — authenticity is permanent
70
+ try:
71
+ signed_payload = _reconstruct_signed_payload(attestation)
72
+ except (TypeError, ValueError):
73
+ raise SignatureVerificationError("Attestation contains invalid JSON fields")
74
+
75
+ try:
76
+ signature = base64.b64decode(signature_b64, validate=True)
77
+ except Exception as e:
78
+ raise SignatureVerificationError(f"Invalid signature encoding: {e}")
79
+
80
+ try:
81
+ VERIFY_KEY.verify(signed_payload, signature)
82
+ except (BadSignatureError, ValueError, TypeError):
83
+ raise SignatureVerificationError(
84
+ "INVALID SIGNATURE: Attestation was not signed by SwiftAPI. "
85
+ "This could indicate a forged or tampered attestation."
86
+ )
87
+
88
+ # Check expiration AFTER signature (execution window, not authenticity)
89
+ if not ignore_expiry:
90
+ try:
91
+ expiry = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
92
+ if expiry.tzinfo is None:
93
+ raise ValueError("Expiration must include a timezone")
94
+ now = datetime.now(timezone.utc)
95
+ if now >= expiry:
96
+ raise AttestationExpiredError(f"Attestation expired at {expires_at}")
97
+ except ValueError as e:
98
+ raise SignatureVerificationError(f"Invalid expiration format: {e}")
99
+
100
+ return True
101
+
102
+
103
+ def _reconstruct_signed_payload(attestation: Dict[str, Any]) -> bytes:
104
+ """
105
+ Reconstruct the exact payload that was signed by SwiftAPI.
106
+
107
+ The server signs the attestation BEFORE adding signature/signing_mode fields.
108
+ We must reconstruct that exact payload.
109
+ """
110
+ # Copy attestation and remove fields that weren't in the signed payload.
111
+ # signature/signing_mode are added after signing.
112
+ # /attest appends action_data and policy diagnostics after signing.
113
+ # They are never used as authorization evidence.
114
+ _unsigned = (
115
+ "signature", "signing_mode", "action_data", "denial_reason",
116
+ "violated_policies", "evaluated_policies", "evaluation_context",
117
+ )
118
+ signed_fields = {k: v for k, v in attestation.items() if k not in _unsigned}
119
+
120
+ # Deterministic JSON serialization (must match server exactly)
121
+ payload_str = json.dumps(signed_fields, sort_keys=True, separators=(",", ":"))
122
+ return payload_str.encode("utf-8")
123
+
124
+
125
+ def _snapshot_action_data(data: Dict[str, Any]) -> Dict[str, Any]:
126
+ """Detach request data and normalize it exactly as JSON transport does."""
127
+ try:
128
+ return json.loads(json.dumps(data, allow_nan=False))
129
+ except (TypeError, ValueError):
130
+ raise SwiftAPIError("Action data must contain JSON-serializable values")
131
+
132
+
133
+ def _action_fingerprint(data: Dict[str, Any], truncated: bool = False) -> str:
134
+ """Match the backend's canonical JSON hash (/attest uses 32 hex chars)."""
135
+ canonical = json.dumps(data, sort_keys=True, separators=(",", ":"))
136
+ fingerprint = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
137
+ return fingerprint[:32] if truncated else fingerprint
138
+
139
+
140
+ def _validate_action_binding(
141
+ attestation: Dict[str, Any], action_type: str, action_fingerprint: str
142
+ ) -> bool:
143
+ """Check signed execution fields after verify_signature has succeeded."""
144
+ if attestation.get("action_type") != action_type:
145
+ raise SignatureVerificationError("Attestation action type does not match the request")
146
+ if attestation.get("action_fingerprint") != action_fingerprint:
147
+ raise SignatureVerificationError("Attestation action fingerprint does not match the request")
148
+ approved = attestation.get("approved")
149
+ if type(approved) is not bool:
150
+ raise SignatureVerificationError("Attestation approval must be a boolean")
151
+ return approved
152
+
153
+
154
+ def get_public_key() -> str:
155
+ """Return SwiftAPI's public key in Base64 format."""
156
+ return SWIFTAPI_PUBLIC_KEY_B64
157
+
158
+
159
+ def is_valid(attestation: Dict[str, Any], ignore_expiry: bool = False) -> bool:
160
+ """
161
+ Check if attestation is valid without raising exceptions.
162
+
163
+ Args:
164
+ attestation: The execution_attestation dict
165
+ ignore_expiry: If True, only check signature authenticity
166
+
167
+ Returns:
168
+ True if valid, False otherwise
169
+ """
170
+ try:
171
+ return verify_signature(attestation, ignore_expiry=ignore_expiry)
172
+ except Exception:
173
+ return False
174
+
175
+
176
+ def claim_attestation(attestation: Dict[str, Any]) -> None:
177
+ """One-use local replay claim; call only after signature and action verification."""
178
+ import os
179
+ from pathlib import Path
180
+ from .brake.core import ReplayLedger
181
+ root = Path(os.environ.get("SWIFTAPI_HOME", Path.home() / ".local/share/swiftapi"))
182
+ ReplayLedger(root).claim(SWIFTAPI_PUBLIC_KEY_B64, attestation["jti"])
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: swiftapi-python
3
+ Version: 1.3.0
4
+ Summary: SwiftAPI Python SDK - AI Action Verification Gateway
5
+ Author-email: Rayan Pal <rayan@swiftapi.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://swiftapi.ai
8
+ Project-URL: Documentation, https://getswiftapi.com/docs
9
+ Keywords: swiftapi,ai,verification,attestation,security,governance,enforcement
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests<3,>=2.32.3
25
+ Requires-Dist: pynacl<2,>=1.5.0
26
+ Requires-Dist: colorama<1,>=0.4.6
27
+ Requires-Dist: tomli<3,>=2; python_version < "3.11"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: hypothesis<7,>=6; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: black>=23.0.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
+ Requires-Dist: types-requests<3,>=2.32.3; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # SwiftAPI Python SDK
38
+
39
+ A prerequisite before a protected action. Local Swift Brake verifies explicit
40
+ permission, executes its registered capability once, and creates signed receipts.
41
+ Codex or Claude Code remains the coding agent. No second model is involved.
42
+
43
+ ```bash
44
+ pip install swiftapi-python
45
+ swiftapi doctor
46
+ swiftapi brake test
47
+ swiftapi brake install --dry-run
48
+ swiftapi brake install
49
+ ```
50
+
51
+ Python 3.9+, macOS and Linux. The local test needs no account, API key, model,
52
+ or cloud service. Restart the coding agent after installing the MCP integration.
53
+
54
+ ## Try the brake
55
+
56
+ ```bash
57
+ swiftapi brake propose hello.txt "Hello from Swift Brake"
58
+ # Copy the returned request_id into the following commands.
59
+ swiftapi brake execute REQUEST_ID
60
+ # GATE_BLOCK: no marker is written.
61
+ swiftapi brake authorize REQUEST_ID
62
+ swiftapi brake execute REQUEST_ID
63
+ # AUTHORIZED_EXECUTION: exact marker bytes are written once.
64
+ swiftapi brake execute REQUEST_ID
65
+ # GATE_BLOCK: the authorization cannot execute again.
66
+ swiftapi status
67
+ swiftapi receipts
68
+ swiftapi verify RECEIPT_PATH --public-key PUBLIC_KEY_PATH
69
+ ```
70
+
71
+ `status` prints the sandbox and public-key path. `execute` returns the receipt
72
+ path. The operator authorizes in a separate terminal; authorization is deliberately
73
+ absent from the MCP tool surface. A signature requires an independently trusted
74
+ verification key, never a key blindly accepted from the receipt itself.
75
+
76
+ `swiftapi brake` presents `swiftapi>` and delegates ordinary input to the existing
77
+ Codex CLI. `swiftapi brake --agent claude` uses Claude Code. These host calls use
78
+ your existing account; the local test itself makes no model calls.
79
+
80
+ ## Python
81
+
82
+ The old imports remain available: `from swiftapi import SwiftAPI, Enforcement`.
83
+ The new local API does not require a remote account:
84
+
85
+ ```python
86
+ from pathlib import Path
87
+ from tempfile import mkdtemp
88
+ from swiftapi import ActionEnvelope, BindingCondition, LocalAuthority, LocalGuard
89
+
90
+ action = ActionEnvelope("example.append", {"value": "verified"}, "Append one value")
91
+ state = {"operator_approved": True} # Supplied by trusted application code.
92
+ condition = BindingCondition(action.action_hash, state)
93
+ guard = LocalGuard(LocalAuthority(), Path(mkdtemp()) / "ledger")
94
+ values = []
95
+ guard.run(
96
+ lambda: values.append("verified"),
97
+ action=action.capability, params=action.params, intent=action.intent,
98
+ condition=condition, state=state,
99
+ )
100
+ assert values == ["verified"]
101
+ ```
102
+
103
+ A callback's code must actually implement its supplied action description.
104
+ For an untrusted agent, use a registered broker operation that constructs and
105
+ executes the same canonical parameters. The bundled MCP operation creates a new
106
+ small UTF-8 marker in its own sandbox; it cannot run a shell or overwrite files.
107
+
108
+ ## What is enforced
109
+
110
+ An authorization binds an action hash, capability, condition hash, state hash,
111
+ nonce, issue time, and expiry. Ed25519 verification and a durable SQLite replay
112
+ claim happen before execution. Execution is never retried. Missing, malformed,
113
+ forged, expired, replayed, or mismatched authorization fails closed.
114
+
115
+ This guarantee applies to mediated capabilities. Arbitrary host tools are outside
116
+ the broker. Local same-user key storage supports development and CI; a malicious
117
+ process able to read the signer, change the gate, or mutate the ledger can bypass
118
+ that trust boundary. Production credentials and signing authority require process
119
+ or account isolation from untrusted agent code. Instructions and hooks are not a
120
+ universal security boundary.
121
+
122
+ ## Research is measured separately
123
+
124
+ `GATE_BLOCK` is deterministic enforcement, not native EOS. Successful empty
125
+ provider output is classified as a black-box `PROVIDER_VOID` with V0/V1/V2/VU
126
+ subtypes. Only exposed native token evidence can qualify as `NATIVE_VOID`.
127
+ The package does not download or retrain research models. The frozen
128
+ [PCCG-2 release](https://getswiftapi.com/pccg-2) and
129
+ [research library](https://getswiftapi.com/research) preserve the empirical,
130
+ causal, theoretical, and protocol boundaries.
131
+
132
+ ## Documentation
133
+
134
+ Start with [Quickstart](docs/QUICKSTART.md). See [concepts](docs/CONCEPTS.md),
135
+ [local brake](docs/LOCAL_BRAKE.md), [Codex](docs/CODEX.md),
136
+ [Claude Code](docs/CLAUDE_CODE.md), [Python SDK](docs/PYTHON_SDK.md),
137
+ [conditions](docs/CONDITIONS.md), [action envelopes](docs/ACTION_ENVELOPES.md),
138
+ [receipts](docs/RECEIPTS.md), [verification](docs/VERIFICATION.md),
139
+ [remote authority](docs/REMOTE_SWIFTAPI.md), [BYOK](docs/BYOK.md),
140
+ [security](docs/SECURITY_MODEL.md), [threat model](docs/THREAT_MODEL.md),
141
+ [native noncontinuation](docs/NATIVE_NONCONTINUATION.md),
142
+ [benchmarking](docs/BENCHMARKING.md), and [provenance](docs/RESEARCH_PROVENANCE.md).
143
+
144
+ [Migration from 1.2.2](MIGRATION_1_2_2.md) documents security corrections and
145
+ termination-metadata changes. Historical 1.2.2 bytes remain immutable on PyPI.
146
+
147
+ MIT license. Research and software by Rayan Pal / SwiftAPI Labs.
@@ -0,0 +1,26 @@
1
+ swiftapi/__init__.py,sha256=oJ91q9TbmnCdPYUuKXPWtW3ne6aQBEo40qANvSWmI_k,1867
2
+ swiftapi/__main__.py,sha256=ee5vE0xcUZM3fPmxicvcw-IJXkm6nOwxARREHBWpF8Q,47
3
+ swiftapi/anthropic.py,sha256=f8lB4XRI4VmCqOLiQaDX5aFNZNzPiR6jheuv40f0umk,8209
4
+ swiftapi/cli.py,sha256=hP9wcYQGQOpwe8LNjdiNQ2GETInRzHumPe5xGhQnCpo,5860
5
+ swiftapi/client.py,sha256=jvAGLGeD-8evyWEWsRGKJZgb_PtQdNgztJR4OQgD48c,11094
6
+ swiftapi/enforcement.py,sha256=EzhR9TVbXe-NqAXxrIfJpg1hSK0gmB9rK--rt2AuL_s,8659
7
+ swiftapi/exceptions.py,sha256=hBleOCc1WwDEM70WNYTy1TDOSOaBJSx3PtzFzfmVOGY,1701
8
+ swiftapi/openai.py,sha256=3eI1T-hF20Apymp6Vkh9jskALmFmQCtzptH4jh9B544,8432
9
+ swiftapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ swiftapi/utils.py,sha256=Sn6ED6ltECCYc-ce9fhxZxgzBthvflPCZelXPPzskZM,3145
11
+ swiftapi/verifier.py,sha256=JkFM9wUFVqy_M98jm4PLWUBtZFTXsItPSGGyZpiqoo0,7058
12
+ swiftapi/brake/__init__.py,sha256=OGhusWHNo8aK1HSsJT6yjEBLpRrQtBJscuRFEj6hv7U,883
13
+ swiftapi/brake/benchmarks.py,sha256=gqURdxdu1FMKCzw8i3c8zXewBzHGdNsMQjiu0DvnFbo,4469
14
+ swiftapi/brake/broker.py,sha256=J5dq9hW1hcBAZ6X1bDkiOr_4R2qZfxQgmTATlwidVF4,10775
15
+ swiftapi/brake/core.py,sha256=_Jd1O-10CVb9-1DzgqCQ3o0D1mvM5SCMlsf6q-bbP6E,14244
16
+ swiftapi/brake/install.py,sha256=VZKGF0r6FfY0CKJ6c_-g_xlGW3TFI4kLooUJq9FYcRM,11137
17
+ swiftapi/brake/mcp.py,sha256=y6VGP37U8IXRYO_bptwo9JOrE6QXkp8Qmojoo32QN6g,5695
18
+ swiftapi/brake/observations.py,sha256=RBkkmzBEzJwq-JG9yYZR9VtsMNocRoOgxCw6iHohG_E,1963
19
+ swiftapi/brake/qualification.py,sha256=tRkx67lKH8c018atUmllWajZwMUccpNf1QptJQIU6ik,4031
20
+ swiftapi/brake/remote.py,sha256=qPj1t0b7uQsQWdTz_3NZz1roxjXH6Mu9ORxVK8amm-4,3150
21
+ swiftapi_python-1.3.0.dist-info/licenses/LICENSE,sha256=btq3h42ZP3swvtQ3ak5k3sP_Y6yRwzL5zrV7EzNeRus,1087
22
+ swiftapi_python-1.3.0.dist-info/METADATA,sha256=FRDqYXMzo9uf9PzJjT9XmyCm64fwc988J-liY3j1qJM,6357
23
+ swiftapi_python-1.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
24
+ swiftapi_python-1.3.0.dist-info/entry_points.txt,sha256=rIhaU6i_Q2sPz0b8a7Hpe7crjULOwJfxdL1-AIcKRAw,47
25
+ swiftapi_python-1.3.0.dist-info/top_level.txt,sha256=oio-d2BTrT91P7dmufUhlSYjOtWBh4bOYz6rsTDDvu4,9
26
+ swiftapi_python-1.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.0)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ swiftapi = swiftapi.cli:main