shebangrun 0.1.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.
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.4
2
+ Name: shebangrun
3
+ Version: 0.1.0
4
+ Summary: Python client library for shebang.run
5
+ Author-email: "shebang.run" <hello@shebang.run>
6
+ License: MIT
7
+ Project-URL: Homepage, https://shebang.run
8
+ Project-URL: Documentation, https://shebang.run/docs
9
+ Project-URL: Repository, https://github.com/shebangrun/python-client
10
+ Keywords: shebang,scripts,automation,devops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.25.0
23
+
24
+ # shebangrun Python Client
25
+
26
+ Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install shebangrun
32
+ ```
33
+
34
+ Or install from source:
35
+
36
+ ```bash
37
+ cd python
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Simple Script Fetching
44
+
45
+ ```python
46
+ from shebangrun import run
47
+
48
+ # Fetch a script (returns content as string)
49
+ content = run(username="mpruitt", script="bashtest")
50
+ print(content)
51
+ ```
52
+
53
+ ### Execute Python Scripts
54
+
55
+ ```python
56
+ from shebangrun import run
57
+
58
+ # Fetch and execute with confirmation prompt
59
+ run(username="mpruitt", script="myscript", eval=True)
60
+
61
+ # Execute without confirmation (use with caution!)
62
+ run(username="mpruitt", script="myscript", eval=True, accept=True)
63
+ ```
64
+
65
+ ### Working with Versions
66
+
67
+ ```python
68
+ from shebangrun import run
69
+
70
+ # Get latest version
71
+ content = run(username="mpruitt", script="deploy", version="latest")
72
+
73
+ # Get specific version
74
+ content = run(username="mpruitt", script="deploy", version="v5")
75
+
76
+ # Get tagged version
77
+ content = run(username="mpruitt", script="deploy", version="dev")
78
+ ```
79
+
80
+ ### Private Scripts
81
+
82
+ ```python
83
+ from shebangrun import run
84
+
85
+ # Access private script with share token
86
+ content = run(
87
+ username="mpruitt",
88
+ script="private-script",
89
+ token="your-share-token-here"
90
+ )
91
+ ```
92
+
93
+ ## Full API Client
94
+
95
+ For more advanced usage, use the `ShebangClient` class:
96
+
97
+ ```python
98
+ from shebangrun import ShebangClient
99
+
100
+ # Initialize client
101
+ client = ShebangClient(url="shebang.run")
102
+
103
+ # Login
104
+ client.login(username="myuser", password="mypassword")
105
+
106
+ # Create a script
107
+ client.create_script(
108
+ name="hello",
109
+ content="#!/bin/bash\necho 'Hello World'",
110
+ description="My first script",
111
+ visibility="public"
112
+ )
113
+
114
+ # List your scripts
115
+ scripts = client.list_scripts()
116
+ for script in scripts:
117
+ print(f"{script['name']} - v{script['version']}")
118
+
119
+ # Update a script (creates new version)
120
+ client.update_script(
121
+ script_id=1,
122
+ content="#!/bin/bash\necho 'Hello World v2'",
123
+ tag="dev"
124
+ )
125
+
126
+ # Generate share token for private script
127
+ token = client.generate_share_token(script_id=1)
128
+ print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
129
+
130
+ # Get script metadata
131
+ meta = client.get_metadata(username="mpruitt", script="bashtest")
132
+ print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
133
+
134
+ # Verify signature
135
+ verification = client.verify_signature(username="mpruitt", script="bashtest")
136
+ print(f"Signed: {verification['signed']}")
137
+ ```
138
+
139
+ ## Key Management
140
+
141
+ ```python
142
+ from shebangrun import ShebangClient
143
+
144
+ client = ShebangClient(url="shebang.run")
145
+ client.login(username="myuser", password="mypassword")
146
+
147
+ # Generate a new keypair
148
+ key = client.generate_key(name="my-signing-key")
149
+ print(f"Public Key: {key['public_key']}")
150
+ print(f"Private Key: {key['private_key']}") # Save this securely!
151
+
152
+ # List keys
153
+ keys = client.list_keys()
154
+ for key in keys:
155
+ print(f"{key['name']} - Created: {key['created_at']}")
156
+
157
+ # Import existing public key
158
+ client.import_key(
159
+ name="imported-key",
160
+ public_key="-----BEGIN PUBLIC KEY-----\n..."
161
+ )
162
+
163
+ # Delete a key
164
+ client.delete_key(key_id=1)
165
+ ```
166
+
167
+ ## Account Management
168
+
169
+ ```python
170
+ from shebangrun import ShebangClient
171
+
172
+ client = ShebangClient(url="shebang.run")
173
+ client.login(username="myuser", password="mypassword")
174
+
175
+ # Change password
176
+ client.change_password(
177
+ current_password="oldpass",
178
+ new_password="newpass"
179
+ )
180
+
181
+ # Export all data (GDPR)
182
+ data = client.export_data()
183
+ print(f"Exported {len(data['scripts'])} scripts")
184
+
185
+ # Delete account (permanent!)
186
+ client.delete_account()
187
+ ```
188
+
189
+ ## API Reference
190
+
191
+ ### `run()` Function
192
+
193
+ ```python
194
+ run(username, script, key=None, eval=False, accept=False,
195
+ url="shebang.run", version=None, token=None)
196
+ ```
197
+
198
+ **Parameters:**
199
+ - `username` (str, required): Script owner's username
200
+ - `script` (str, required): Script name
201
+ - `key` (str, optional): Private key for decryption (not yet implemented)
202
+ - `eval` (bool, optional): Execute the script in Python (default: False)
203
+ - `accept` (bool, optional): Skip confirmation when eval=True (default: False)
204
+ - `url` (str, optional): Base URL (default: "shebang.run")
205
+ - `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
206
+ - `token` (str, optional): Share token for private scripts
207
+
208
+ **Returns:**
209
+ - String content if `eval=False`
210
+ - Execution result if `eval=True`
211
+
212
+ ### `ShebangClient` Class
213
+
214
+ #### Authentication
215
+ - `register(username, email, password)` - Register new user
216
+ - `login(username, password)` - Login and get JWT token
217
+
218
+ #### Script Management
219
+ - `list_scripts()` - List user's scripts
220
+ - `get_script(username, script, version=None, token=None)` - Fetch script content
221
+ - `get_metadata(username, script)` - Get script metadata
222
+ - `verify_signature(username, script)` - Verify script signature
223
+ - `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
224
+ - `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
225
+ - `delete_script(script_id)` - Delete script
226
+ - `generate_share_token(script_id)` - Generate share token
227
+ - `revoke_share_token(script_id, token)` - Revoke share token
228
+
229
+ #### Key Management
230
+ - `list_keys()` - List keypairs
231
+ - `generate_key(name)` - Generate new keypair
232
+ - `import_key(name, public_key)` - Import public key
233
+ - `delete_key(key_id)` - Delete keypair
234
+
235
+ #### Account Management
236
+ - `change_password(current_password, new_password)` - Change password
237
+ - `export_data()` - Export all data (GDPR)
238
+ - `delete_account()` - Delete account
239
+
240
+ ## Security Notes
241
+
242
+ ⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
243
+
244
+ ✅ **Best Practices:**
245
+ - Always review scripts before executing with `eval=True`
246
+ - Use `accept=False` (default) to see the script before execution
247
+ - Store private keys securely, never commit them to version control
248
+ - Use environment variables for tokens and credentials
249
+ - Verify script signatures when available
250
+
251
+ ## Examples
252
+
253
+ ### Automation Script
254
+
255
+ ```python
256
+ #!/usr/bin/env python3
257
+ from shebangrun import run
258
+
259
+ # Fetch deployment script and execute with confirmation
260
+ run(
261
+ username="devops",
262
+ script="deploy-prod",
263
+ version="latest",
264
+ eval=True,
265
+ accept=False # Always confirm production deployments!
266
+ )
267
+ ```
268
+
269
+ ### CI/CD Integration
270
+
271
+ ```python
272
+ import os
273
+ from shebangrun import ShebangClient
274
+
275
+ client = ShebangClient()
276
+ client.login(
277
+ username=os.environ["SHEBANG_USER"],
278
+ password=os.environ["SHEBANG_PASS"]
279
+ )
280
+
281
+ # Update deployment script
282
+ client.update_script(
283
+ script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
284
+ content=open("deploy.sh").read(),
285
+ tag="latest"
286
+ )
287
+
288
+ print("Deployment script updated!")
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT
294
+
295
+ ## Links
296
+
297
+ - Website: https://shebang.run
298
+ - Documentation: https://shebang.run/docs
299
+ - GitHub: https://github.com/shebangrun/python-client
@@ -0,0 +1,276 @@
1
+ # shebangrun Python Client
2
+
3
+ Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install shebangrun
9
+ ```
10
+
11
+ Or install from source:
12
+
13
+ ```bash
14
+ cd python
15
+ pip install -e .
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### Simple Script Fetching
21
+
22
+ ```python
23
+ from shebangrun import run
24
+
25
+ # Fetch a script (returns content as string)
26
+ content = run(username="mpruitt", script="bashtest")
27
+ print(content)
28
+ ```
29
+
30
+ ### Execute Python Scripts
31
+
32
+ ```python
33
+ from shebangrun import run
34
+
35
+ # Fetch and execute with confirmation prompt
36
+ run(username="mpruitt", script="myscript", eval=True)
37
+
38
+ # Execute without confirmation (use with caution!)
39
+ run(username="mpruitt", script="myscript", eval=True, accept=True)
40
+ ```
41
+
42
+ ### Working with Versions
43
+
44
+ ```python
45
+ from shebangrun import run
46
+
47
+ # Get latest version
48
+ content = run(username="mpruitt", script="deploy", version="latest")
49
+
50
+ # Get specific version
51
+ content = run(username="mpruitt", script="deploy", version="v5")
52
+
53
+ # Get tagged version
54
+ content = run(username="mpruitt", script="deploy", version="dev")
55
+ ```
56
+
57
+ ### Private Scripts
58
+
59
+ ```python
60
+ from shebangrun import run
61
+
62
+ # Access private script with share token
63
+ content = run(
64
+ username="mpruitt",
65
+ script="private-script",
66
+ token="your-share-token-here"
67
+ )
68
+ ```
69
+
70
+ ## Full API Client
71
+
72
+ For more advanced usage, use the `ShebangClient` class:
73
+
74
+ ```python
75
+ from shebangrun import ShebangClient
76
+
77
+ # Initialize client
78
+ client = ShebangClient(url="shebang.run")
79
+
80
+ # Login
81
+ client.login(username="myuser", password="mypassword")
82
+
83
+ # Create a script
84
+ client.create_script(
85
+ name="hello",
86
+ content="#!/bin/bash\necho 'Hello World'",
87
+ description="My first script",
88
+ visibility="public"
89
+ )
90
+
91
+ # List your scripts
92
+ scripts = client.list_scripts()
93
+ for script in scripts:
94
+ print(f"{script['name']} - v{script['version']}")
95
+
96
+ # Update a script (creates new version)
97
+ client.update_script(
98
+ script_id=1,
99
+ content="#!/bin/bash\necho 'Hello World v2'",
100
+ tag="dev"
101
+ )
102
+
103
+ # Generate share token for private script
104
+ token = client.generate_share_token(script_id=1)
105
+ print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
106
+
107
+ # Get script metadata
108
+ meta = client.get_metadata(username="mpruitt", script="bashtest")
109
+ print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
110
+
111
+ # Verify signature
112
+ verification = client.verify_signature(username="mpruitt", script="bashtest")
113
+ print(f"Signed: {verification['signed']}")
114
+ ```
115
+
116
+ ## Key Management
117
+
118
+ ```python
119
+ from shebangrun import ShebangClient
120
+
121
+ client = ShebangClient(url="shebang.run")
122
+ client.login(username="myuser", password="mypassword")
123
+
124
+ # Generate a new keypair
125
+ key = client.generate_key(name="my-signing-key")
126
+ print(f"Public Key: {key['public_key']}")
127
+ print(f"Private Key: {key['private_key']}") # Save this securely!
128
+
129
+ # List keys
130
+ keys = client.list_keys()
131
+ for key in keys:
132
+ print(f"{key['name']} - Created: {key['created_at']}")
133
+
134
+ # Import existing public key
135
+ client.import_key(
136
+ name="imported-key",
137
+ public_key="-----BEGIN PUBLIC KEY-----\n..."
138
+ )
139
+
140
+ # Delete a key
141
+ client.delete_key(key_id=1)
142
+ ```
143
+
144
+ ## Account Management
145
+
146
+ ```python
147
+ from shebangrun import ShebangClient
148
+
149
+ client = ShebangClient(url="shebang.run")
150
+ client.login(username="myuser", password="mypassword")
151
+
152
+ # Change password
153
+ client.change_password(
154
+ current_password="oldpass",
155
+ new_password="newpass"
156
+ )
157
+
158
+ # Export all data (GDPR)
159
+ data = client.export_data()
160
+ print(f"Exported {len(data['scripts'])} scripts")
161
+
162
+ # Delete account (permanent!)
163
+ client.delete_account()
164
+ ```
165
+
166
+ ## API Reference
167
+
168
+ ### `run()` Function
169
+
170
+ ```python
171
+ run(username, script, key=None, eval=False, accept=False,
172
+ url="shebang.run", version=None, token=None)
173
+ ```
174
+
175
+ **Parameters:**
176
+ - `username` (str, required): Script owner's username
177
+ - `script` (str, required): Script name
178
+ - `key` (str, optional): Private key for decryption (not yet implemented)
179
+ - `eval` (bool, optional): Execute the script in Python (default: False)
180
+ - `accept` (bool, optional): Skip confirmation when eval=True (default: False)
181
+ - `url` (str, optional): Base URL (default: "shebang.run")
182
+ - `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
183
+ - `token` (str, optional): Share token for private scripts
184
+
185
+ **Returns:**
186
+ - String content if `eval=False`
187
+ - Execution result if `eval=True`
188
+
189
+ ### `ShebangClient` Class
190
+
191
+ #### Authentication
192
+ - `register(username, email, password)` - Register new user
193
+ - `login(username, password)` - Login and get JWT token
194
+
195
+ #### Script Management
196
+ - `list_scripts()` - List user's scripts
197
+ - `get_script(username, script, version=None, token=None)` - Fetch script content
198
+ - `get_metadata(username, script)` - Get script metadata
199
+ - `verify_signature(username, script)` - Verify script signature
200
+ - `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
201
+ - `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
202
+ - `delete_script(script_id)` - Delete script
203
+ - `generate_share_token(script_id)` - Generate share token
204
+ - `revoke_share_token(script_id, token)` - Revoke share token
205
+
206
+ #### Key Management
207
+ - `list_keys()` - List keypairs
208
+ - `generate_key(name)` - Generate new keypair
209
+ - `import_key(name, public_key)` - Import public key
210
+ - `delete_key(key_id)` - Delete keypair
211
+
212
+ #### Account Management
213
+ - `change_password(current_password, new_password)` - Change password
214
+ - `export_data()` - Export all data (GDPR)
215
+ - `delete_account()` - Delete account
216
+
217
+ ## Security Notes
218
+
219
+ ⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
220
+
221
+ ✅ **Best Practices:**
222
+ - Always review scripts before executing with `eval=True`
223
+ - Use `accept=False` (default) to see the script before execution
224
+ - Store private keys securely, never commit them to version control
225
+ - Use environment variables for tokens and credentials
226
+ - Verify script signatures when available
227
+
228
+ ## Examples
229
+
230
+ ### Automation Script
231
+
232
+ ```python
233
+ #!/usr/bin/env python3
234
+ from shebangrun import run
235
+
236
+ # Fetch deployment script and execute with confirmation
237
+ run(
238
+ username="devops",
239
+ script="deploy-prod",
240
+ version="latest",
241
+ eval=True,
242
+ accept=False # Always confirm production deployments!
243
+ )
244
+ ```
245
+
246
+ ### CI/CD Integration
247
+
248
+ ```python
249
+ import os
250
+ from shebangrun import ShebangClient
251
+
252
+ client = ShebangClient()
253
+ client.login(
254
+ username=os.environ["SHEBANG_USER"],
255
+ password=os.environ["SHEBANG_PASS"]
256
+ )
257
+
258
+ # Update deployment script
259
+ client.update_script(
260
+ script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
261
+ content=open("deploy.sh").read(),
262
+ tag="latest"
263
+ )
264
+
265
+ print("Deployment script updated!")
266
+ ```
267
+
268
+ ## License
269
+
270
+ MIT
271
+
272
+ ## Links
273
+
274
+ - Website: https://shebang.run
275
+ - Documentation: https://shebang.run/docs
276
+ - GitHub: https://github.com/shebangrun/python-client
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shebangrun"
7
+ version = "0.1.0"
8
+ description = "Python client library for shebang.run"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "shebang.run", email = "hello@shebang.run"}
14
+ ]
15
+ keywords = ["shebang", "scripts", "automation", "devops"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ ]
27
+ dependencies = [
28
+ "requests>=2.25.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://shebang.run"
33
+ Documentation = "https://shebang.run/docs"
34
+ Repository = "https://github.com/shebangrun/python-client"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """
2
+ shebangrun - Python client library for shebang.run
3
+
4
+ A helper library to interact with shebang.run API and execute remote scripts.
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ from .client import ShebangClient, run
10
+
11
+ __all__ = ["ShebangClient", "run"]
@@ -0,0 +1,378 @@
1
+ """
2
+ Main client for interacting with shebang.run
3
+ """
4
+
5
+ import requests
6
+ from typing import Optional, Any
7
+
8
+
9
+ class ShebangClient:
10
+ """Client for interacting with shebang.run API"""
11
+
12
+ def __init__(self, url: str = "shebang.run", token: Optional[str] = None):
13
+ """
14
+ Initialize the client
15
+
16
+ Args:
17
+ url: Base URL (default: shebang.run)
18
+ token: JWT token for authenticated requests
19
+ """
20
+ self.base_url = f"https://{url}"
21
+ self.token = token
22
+ self.session = requests.Session()
23
+ if token:
24
+ self.session.headers.update({"Authorization": f"Bearer {token}"})
25
+
26
+ def get_script(self, username: str, script: str, version: Optional[str] = None,
27
+ token: Optional[str] = None) -> tuple:
28
+ """
29
+ Retrieve a script from shebang.run
30
+
31
+ Args:
32
+ username: Script owner's username
33
+ script: Script name
34
+ version: Optional version tag (@latest, @v1, @dev, etc.)
35
+ token: Optional share token for private scripts
36
+
37
+ Returns:
38
+ Tuple of (content, metadata) where metadata includes encryption info
39
+ """
40
+ script_path = f"{script}@{version}" if version else script
41
+ url = f"{self.base_url}/{username}/{script_path}"
42
+
43
+ params = {}
44
+ if token:
45
+ params["token"] = token
46
+
47
+ response = self.session.get(url, params=params)
48
+ response.raise_for_status()
49
+
50
+ metadata = {
51
+ "encrypted": response.headers.get("X-Encrypted") == "true",
52
+ "version": response.headers.get("X-Script-Version"),
53
+ "checksum": response.headers.get("X-Script-Checksum"),
54
+ "key_id": response.headers.get("X-Encryption-KeyID"),
55
+ "wrapped_key": response.headers.get("X-Wrapped-Key")
56
+ }
57
+
58
+ return response.content if metadata["encrypted"] else response.text, metadata
59
+
60
+ def get_metadata(self, username: str, script: str) -> dict:
61
+ """Get script metadata"""
62
+ url = f"{self.base_url}/{username}/{script}/meta"
63
+ response = self.session.get(url)
64
+ response.raise_for_status()
65
+ return response.json()
66
+
67
+ def verify_signature(self, username: str, script: str) -> dict:
68
+ """Verify script signature"""
69
+ url = f"{self.base_url}/{username}/{script}/verify"
70
+ response = self.session.get(url)
71
+ response.raise_for_status()
72
+ return response.json()
73
+
74
+ # Authentication
75
+ def register(self, username: str, email: str, password: str) -> dict:
76
+ """Register a new user"""
77
+ url = f"{self.base_url}/api/auth/register"
78
+ response = self.session.post(url, json={
79
+ "username": username,
80
+ "email": email,
81
+ "password": password
82
+ })
83
+ response.raise_for_status()
84
+ data = response.json()
85
+ if "token" in data:
86
+ self.token = data["token"]
87
+ self.session.headers.update({"Authorization": f"Bearer {self.token}"})
88
+ return data
89
+
90
+ def login(self, username: str, password: str) -> dict:
91
+ """Login and get JWT token"""
92
+ url = f"{self.base_url}/api/auth/login"
93
+ response = self.session.post(url, json={
94
+ "username": username,
95
+ "password": password
96
+ })
97
+ response.raise_for_status()
98
+ data = response.json()
99
+ if "token" in data:
100
+ self.token = data["token"]
101
+ self.session.headers.update({"Authorization": f"Bearer {self.token}"})
102
+ return data
103
+
104
+ # Script Management
105
+ def list_scripts(self) -> list:
106
+ """List user's scripts (requires authentication)"""
107
+ url = f"{self.base_url}/api/scripts"
108
+ response = self.session.get(url)
109
+ response.raise_for_status()
110
+ return response.json()
111
+
112
+ def create_script(self, name: str, content: str, description: str = "",
113
+ visibility: str = "private", keypair_id: Optional[int] = None) -> dict:
114
+ """Create a new script"""
115
+ url = f"{self.base_url}/api/scripts"
116
+ data = {
117
+ "name": name,
118
+ "content": content,
119
+ "description": description,
120
+ "visibility": visibility
121
+ }
122
+ if keypair_id:
123
+ data["keypair_id"] = keypair_id
124
+
125
+ response = self.session.post(url, json=data)
126
+ response.raise_for_status()
127
+ return response.json()
128
+
129
+ def update_script(self, script_id: int, content: Optional[str] = None,
130
+ description: Optional[str] = None, visibility: Optional[str] = None,
131
+ tag: Optional[str] = None, keypair_id: Optional[int] = None) -> dict:
132
+ """Update a script (creates new version if content changed)"""
133
+ url = f"{self.base_url}/api/scripts/{script_id}"
134
+ data = {}
135
+ if content:
136
+ data["content"] = content
137
+ if description:
138
+ data["description"] = description
139
+ if visibility:
140
+ data["visibility"] = visibility
141
+ if tag:
142
+ data["tag"] = tag
143
+ if keypair_id:
144
+ data["keypair_id"] = keypair_id
145
+
146
+ response = self.session.put(url, json=data)
147
+ response.raise_for_status()
148
+ return response.json() if response.text else {}
149
+
150
+ def delete_script(self, script_id: int):
151
+ """Delete a script"""
152
+ url = f"{self.base_url}/api/scripts/{script_id}"
153
+ response = self.session.delete(url)
154
+ response.raise_for_status()
155
+
156
+ def generate_share_token(self, script_id: int) -> str:
157
+ """Generate a share token for a private script"""
158
+ url = f"{self.base_url}/api/scripts/{script_id}/share"
159
+ response = self.session.post(url)
160
+ response.raise_for_status()
161
+ return response.json()["token"]
162
+
163
+ def revoke_share_token(self, script_id: int, token: str):
164
+ """Revoke a share token"""
165
+ url = f"{self.base_url}/api/scripts/{script_id}/share/{token}"
166
+ response = self.session.delete(url)
167
+ response.raise_for_status()
168
+
169
+ # Key Management
170
+ def list_keys(self) -> list:
171
+ """List user's keypairs"""
172
+ url = f"{self.base_url}/api/keys"
173
+ response = self.session.get(url)
174
+ response.raise_for_status()
175
+ return response.json()
176
+
177
+ def generate_key(self, name: str) -> dict:
178
+ """Generate a new keypair (returns private key - save it!)"""
179
+ url = f"{self.base_url}/api/keys/generate"
180
+ response = self.session.post(url, json={"name": name})
181
+ response.raise_for_status()
182
+ return response.json()
183
+
184
+ def import_key(self, name: str, public_key: str) -> dict:
185
+ """Import an existing public key"""
186
+ url = f"{self.base_url}/api/keys/import"
187
+ response = self.session.post(url, json={
188
+ "name": name,
189
+ "public_key": public_key
190
+ })
191
+ response.raise_for_status()
192
+ return response.json()
193
+
194
+ def delete_key(self, key_id: int):
195
+ """Delete a keypair"""
196
+ url = f"{self.base_url}/api/keys/{key_id}"
197
+ response = self.session.delete(url)
198
+ response.raise_for_status()
199
+
200
+ # Account Management
201
+ def change_password(self, current_password: str, new_password: str):
202
+ """Change account password"""
203
+ url = f"{self.base_url}/api/account/password"
204
+ response = self.session.put(url, json={
205
+ "current_password": current_password,
206
+ "new_password": new_password
207
+ })
208
+ response.raise_for_status()
209
+
210
+ def export_data(self) -> dict:
211
+ """Export all user data (GDPR)"""
212
+ url = f"{self.base_url}/api/account/export"
213
+ response = self.session.get(url)
214
+ response.raise_for_status()
215
+ return response.json()
216
+
217
+ def delete_account(self):
218
+ """Delete account permanently"""
219
+ url = f"{self.base_url}/api/account"
220
+ response = self.session.delete(url)
221
+ response.raise_for_status()
222
+
223
+
224
+ def run(username: str, script: str, key: Optional[str] = None,
225
+ eval: bool = False, accept: bool = False, url: str = "shebang.run",
226
+ version: Optional[str] = None, token: Optional[str] = None) -> Any:
227
+ """
228
+ Convenience function to fetch and optionally execute a script
229
+
230
+ Args:
231
+ username: Script owner's username (required)
232
+ script: Script name (required)
233
+ key: Private key contents for decryption (optional)
234
+ eval: If True, evaluate the script in Python (default: False)
235
+ accept: If True, skip confirmation prompt when eval=True (default: False)
236
+ url: Base URL (default: shebang.run)
237
+ version: Version tag (optional, e.g., "latest", "v1", "dev")
238
+ token: Share token for private scripts (optional)
239
+
240
+ Returns:
241
+ Script content as string if eval=False, or result of eval() if eval=True
242
+
243
+ Examples:
244
+ # Just fetch the script
245
+ content = run(username="mpruitt", script="bashtest")
246
+
247
+ # Fetch encrypted script with private key
248
+ content = run(username="mpruitt", script="private", key="-----BEGIN PRIVATE KEY-----\\n...")
249
+
250
+ # Fetch and evaluate with confirmation
251
+ run(username="mpruitt", script="myscript", eval=True)
252
+ """
253
+ client = ShebangClient(url=url)
254
+
255
+ # Fetch the script
256
+ content, metadata = client.get_script(username, script, version=version, token=token)
257
+
258
+ # If encrypted and key is provided, decrypt
259
+ if metadata.get("encrypted") and key:
260
+ try:
261
+ from cryptography.hazmat.primitives import serialization, hashes
262
+ from cryptography.hazmat.primitives.asymmetric import padding
263
+ from cryptography.hazmat.backends import default_backend
264
+ import nacl.bindings
265
+
266
+ # Parse private key
267
+ private_key = serialization.load_pem_private_key(
268
+ key.encode() if isinstance(key, str) else key,
269
+ password=None,
270
+ backend=default_backend()
271
+ )
272
+
273
+ # Get wrapped key from headers
274
+ wrapped_key_hex = metadata.get("wrapped_key")
275
+ if not wrapped_key_hex:
276
+ raise Exception("No wrapped key found in response")
277
+
278
+ # Unwrap symmetric key with RSA private key
279
+ wrapped_key = bytes.fromhex(wrapped_key_hex)
280
+ symmetric_key = private_key.decrypt(
281
+ wrapped_key,
282
+ padding.OAEP(
283
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
284
+ algorithm=hashes.SHA256(),
285
+ label=None
286
+ )
287
+ )
288
+
289
+ # Decrypt content with XChaCha20-Poly1305
290
+ nonce_size = 24
291
+ nonce = content[:nonce_size]
292
+ ciphertext = content[nonce_size:]
293
+
294
+ decrypted = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(
295
+ ciphertext,
296
+ None, # no additional data
297
+ nonce,
298
+ symmetric_key
299
+ )
300
+
301
+ content = decrypted.decode('utf-8')
302
+
303
+ except ImportError:
304
+ raise ImportError("Decryption requires: pip install cryptography pynacl")
305
+ except Exception as e:
306
+ raise Exception(f"Decryption failed: {e}")
307
+ elif metadata.get("encrypted") and not key:
308
+ # Return encrypted bytes if no key provided
309
+ return content
310
+
311
+ # Convert bytes to string if needed
312
+ if isinstance(content, bytes):
313
+ content = content.decode('utf-8')
314
+
315
+ # If not evaluating, just return the content
316
+ if not eval:
317
+ return content
318
+
319
+ # If evaluating, show confirmation unless accept=True
320
+ if not accept:
321
+ print("=" * 60)
322
+ print(f"Script: {username}/{script}")
323
+ print("=" * 60)
324
+ print(content)
325
+ print("=" * 60)
326
+ response = input("Execute this script? (y/N): ").strip().lower()
327
+ if response != 'y':
328
+ print("Execution cancelled.")
329
+ return None
330
+
331
+ # Execute the script
332
+ try:
333
+ import subprocess
334
+ import tempfile
335
+ import os
336
+ import sys
337
+
338
+ # Check for shebang
339
+ lines = content.split('\n')
340
+ if lines[0].startswith('#!'):
341
+ shebang = lines[0].lower()
342
+
343
+ # If shebang contains 'python', execute in current context with exec
344
+ if 'python' in shebang:
345
+ exec_globals = {}
346
+ exec(content, exec_globals)
347
+ return exec_globals
348
+ else:
349
+ # Execute with specified interpreter (bash, sh, etc.)
350
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as f:
351
+ f.write(content)
352
+ temp_path = f.name
353
+
354
+ try:
355
+ os.chmod(temp_path, 0o755)
356
+ result = subprocess.run(
357
+ [temp_path],
358
+ capture_output=True,
359
+ text=True,
360
+ shell=False
361
+ )
362
+
363
+ if result.stdout:
364
+ print(result.stdout, end='')
365
+ if result.stderr:
366
+ print(result.stderr, end='', file=sys.stderr)
367
+
368
+ return result.returncode
369
+ finally:
370
+ os.unlink(temp_path)
371
+ else:
372
+ # No shebang, execute as Python
373
+ exec_globals = {}
374
+ exec(content, exec_globals)
375
+ return exec_globals
376
+ except Exception as e:
377
+ print(f"Error executing script: {e}")
378
+ raise
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.4
2
+ Name: shebangrun
3
+ Version: 0.1.0
4
+ Summary: Python client library for shebang.run
5
+ Author-email: "shebang.run" <hello@shebang.run>
6
+ License: MIT
7
+ Project-URL: Homepage, https://shebang.run
8
+ Project-URL: Documentation, https://shebang.run/docs
9
+ Project-URL: Repository, https://github.com/shebangrun/python-client
10
+ Keywords: shebang,scripts,automation,devops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.25.0
23
+
24
+ # shebangrun Python Client
25
+
26
+ Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install shebangrun
32
+ ```
33
+
34
+ Or install from source:
35
+
36
+ ```bash
37
+ cd python
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Simple Script Fetching
44
+
45
+ ```python
46
+ from shebangrun import run
47
+
48
+ # Fetch a script (returns content as string)
49
+ content = run(username="mpruitt", script="bashtest")
50
+ print(content)
51
+ ```
52
+
53
+ ### Execute Python Scripts
54
+
55
+ ```python
56
+ from shebangrun import run
57
+
58
+ # Fetch and execute with confirmation prompt
59
+ run(username="mpruitt", script="myscript", eval=True)
60
+
61
+ # Execute without confirmation (use with caution!)
62
+ run(username="mpruitt", script="myscript", eval=True, accept=True)
63
+ ```
64
+
65
+ ### Working with Versions
66
+
67
+ ```python
68
+ from shebangrun import run
69
+
70
+ # Get latest version
71
+ content = run(username="mpruitt", script="deploy", version="latest")
72
+
73
+ # Get specific version
74
+ content = run(username="mpruitt", script="deploy", version="v5")
75
+
76
+ # Get tagged version
77
+ content = run(username="mpruitt", script="deploy", version="dev")
78
+ ```
79
+
80
+ ### Private Scripts
81
+
82
+ ```python
83
+ from shebangrun import run
84
+
85
+ # Access private script with share token
86
+ content = run(
87
+ username="mpruitt",
88
+ script="private-script",
89
+ token="your-share-token-here"
90
+ )
91
+ ```
92
+
93
+ ## Full API Client
94
+
95
+ For more advanced usage, use the `ShebangClient` class:
96
+
97
+ ```python
98
+ from shebangrun import ShebangClient
99
+
100
+ # Initialize client
101
+ client = ShebangClient(url="shebang.run")
102
+
103
+ # Login
104
+ client.login(username="myuser", password="mypassword")
105
+
106
+ # Create a script
107
+ client.create_script(
108
+ name="hello",
109
+ content="#!/bin/bash\necho 'Hello World'",
110
+ description="My first script",
111
+ visibility="public"
112
+ )
113
+
114
+ # List your scripts
115
+ scripts = client.list_scripts()
116
+ for script in scripts:
117
+ print(f"{script['name']} - v{script['version']}")
118
+
119
+ # Update a script (creates new version)
120
+ client.update_script(
121
+ script_id=1,
122
+ content="#!/bin/bash\necho 'Hello World v2'",
123
+ tag="dev"
124
+ )
125
+
126
+ # Generate share token for private script
127
+ token = client.generate_share_token(script_id=1)
128
+ print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
129
+
130
+ # Get script metadata
131
+ meta = client.get_metadata(username="mpruitt", script="bashtest")
132
+ print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
133
+
134
+ # Verify signature
135
+ verification = client.verify_signature(username="mpruitt", script="bashtest")
136
+ print(f"Signed: {verification['signed']}")
137
+ ```
138
+
139
+ ## Key Management
140
+
141
+ ```python
142
+ from shebangrun import ShebangClient
143
+
144
+ client = ShebangClient(url="shebang.run")
145
+ client.login(username="myuser", password="mypassword")
146
+
147
+ # Generate a new keypair
148
+ key = client.generate_key(name="my-signing-key")
149
+ print(f"Public Key: {key['public_key']}")
150
+ print(f"Private Key: {key['private_key']}") # Save this securely!
151
+
152
+ # List keys
153
+ keys = client.list_keys()
154
+ for key in keys:
155
+ print(f"{key['name']} - Created: {key['created_at']}")
156
+
157
+ # Import existing public key
158
+ client.import_key(
159
+ name="imported-key",
160
+ public_key="-----BEGIN PUBLIC KEY-----\n..."
161
+ )
162
+
163
+ # Delete a key
164
+ client.delete_key(key_id=1)
165
+ ```
166
+
167
+ ## Account Management
168
+
169
+ ```python
170
+ from shebangrun import ShebangClient
171
+
172
+ client = ShebangClient(url="shebang.run")
173
+ client.login(username="myuser", password="mypassword")
174
+
175
+ # Change password
176
+ client.change_password(
177
+ current_password="oldpass",
178
+ new_password="newpass"
179
+ )
180
+
181
+ # Export all data (GDPR)
182
+ data = client.export_data()
183
+ print(f"Exported {len(data['scripts'])} scripts")
184
+
185
+ # Delete account (permanent!)
186
+ client.delete_account()
187
+ ```
188
+
189
+ ## API Reference
190
+
191
+ ### `run()` Function
192
+
193
+ ```python
194
+ run(username, script, key=None, eval=False, accept=False,
195
+ url="shebang.run", version=None, token=None)
196
+ ```
197
+
198
+ **Parameters:**
199
+ - `username` (str, required): Script owner's username
200
+ - `script` (str, required): Script name
201
+ - `key` (str, optional): Private key for decryption (not yet implemented)
202
+ - `eval` (bool, optional): Execute the script in Python (default: False)
203
+ - `accept` (bool, optional): Skip confirmation when eval=True (default: False)
204
+ - `url` (str, optional): Base URL (default: "shebang.run")
205
+ - `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
206
+ - `token` (str, optional): Share token for private scripts
207
+
208
+ **Returns:**
209
+ - String content if `eval=False`
210
+ - Execution result if `eval=True`
211
+
212
+ ### `ShebangClient` Class
213
+
214
+ #### Authentication
215
+ - `register(username, email, password)` - Register new user
216
+ - `login(username, password)` - Login and get JWT token
217
+
218
+ #### Script Management
219
+ - `list_scripts()` - List user's scripts
220
+ - `get_script(username, script, version=None, token=None)` - Fetch script content
221
+ - `get_metadata(username, script)` - Get script metadata
222
+ - `verify_signature(username, script)` - Verify script signature
223
+ - `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
224
+ - `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
225
+ - `delete_script(script_id)` - Delete script
226
+ - `generate_share_token(script_id)` - Generate share token
227
+ - `revoke_share_token(script_id, token)` - Revoke share token
228
+
229
+ #### Key Management
230
+ - `list_keys()` - List keypairs
231
+ - `generate_key(name)` - Generate new keypair
232
+ - `import_key(name, public_key)` - Import public key
233
+ - `delete_key(key_id)` - Delete keypair
234
+
235
+ #### Account Management
236
+ - `change_password(current_password, new_password)` - Change password
237
+ - `export_data()` - Export all data (GDPR)
238
+ - `delete_account()` - Delete account
239
+
240
+ ## Security Notes
241
+
242
+ ⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
243
+
244
+ ✅ **Best Practices:**
245
+ - Always review scripts before executing with `eval=True`
246
+ - Use `accept=False` (default) to see the script before execution
247
+ - Store private keys securely, never commit them to version control
248
+ - Use environment variables for tokens and credentials
249
+ - Verify script signatures when available
250
+
251
+ ## Examples
252
+
253
+ ### Automation Script
254
+
255
+ ```python
256
+ #!/usr/bin/env python3
257
+ from shebangrun import run
258
+
259
+ # Fetch deployment script and execute with confirmation
260
+ run(
261
+ username="devops",
262
+ script="deploy-prod",
263
+ version="latest",
264
+ eval=True,
265
+ accept=False # Always confirm production deployments!
266
+ )
267
+ ```
268
+
269
+ ### CI/CD Integration
270
+
271
+ ```python
272
+ import os
273
+ from shebangrun import ShebangClient
274
+
275
+ client = ShebangClient()
276
+ client.login(
277
+ username=os.environ["SHEBANG_USER"],
278
+ password=os.environ["SHEBANG_PASS"]
279
+ )
280
+
281
+ # Update deployment script
282
+ client.update_script(
283
+ script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
284
+ content=open("deploy.sh").read(),
285
+ tag="latest"
286
+ )
287
+
288
+ print("Deployment script updated!")
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT
294
+
295
+ ## Links
296
+
297
+ - Website: https://shebang.run
298
+ - Documentation: https://shebang.run/docs
299
+ - GitHub: https://github.com/shebangrun/python-client
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ shebangrun/__init__.py
4
+ shebangrun/client.py
5
+ shebangrun.egg-info/PKG-INFO
6
+ shebangrun.egg-info/SOURCES.txt
7
+ shebangrun.egg-info/dependency_links.txt
8
+ shebangrun.egg-info/requires.txt
9
+ shebangrun.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.25.0
@@ -0,0 +1 @@
1
+ shebangrun