drown 0.1.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.
drown/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Drown Platform CLI - Manage your self-hosted PaaS from the command line."""
2
+
3
+ __version__ = "0.1.0"
drown/api.py ADDED
@@ -0,0 +1,130 @@
1
+ """API wrapper functions for Drown Platform."""
2
+
3
+ import requests
4
+ from drown.config import get_api_base
5
+
6
+
7
+ def _make_request(method, endpoint, token=None, data=None):
8
+ """
9
+ Make an HTTP request to the API.
10
+
11
+ Returns: (success: bool, result: dict)
12
+ """
13
+ api_base = get_api_base()
14
+ url = f"{api_base}{endpoint}"
15
+
16
+ headers = {}
17
+ if token:
18
+ headers["Authorization"] = f"Bearer {token}"
19
+
20
+ try:
21
+ if method == "GET":
22
+ response = requests.get(url, headers=headers, timeout=10)
23
+ elif method == "POST":
24
+ headers["Content-Type"] = "application/json"
25
+ response = requests.post(url, headers=headers, json=data, timeout=10)
26
+ else:
27
+ return False, {"error": f"Unsupported HTTP method: {method}"}
28
+
29
+ # Handle different status codes
30
+ if response.status_code == 200:
31
+ try:
32
+ return True, response.json()
33
+ except ValueError:
34
+ return True, {"message": response.text}
35
+ elif response.status_code == 201:
36
+ try:
37
+ return True, response.json()
38
+ except ValueError:
39
+ return True, {"message": "Created successfully"}
40
+ elif response.status_code == 401:
41
+ return False, {"error": "Invalid credentials. Please run 'drown login' again."}
42
+ elif response.status_code == 403:
43
+ return False, {"error": "Forbidden: You don't have access to this resource."}
44
+ elif response.status_code == 404:
45
+ return False, {"error": "Not found. The app or resource doesn't exist."}
46
+ elif response.status_code == 400:
47
+ try:
48
+ error_data = response.json()
49
+ error_msg = error_data.get("error", "Bad request")
50
+ except ValueError:
51
+ error_msg = "Bad request"
52
+ return False, {"error": error_msg}
53
+ elif response.status_code >= 500:
54
+ return False, {"error": "Server error. Please try again later."}
55
+ else:
56
+ return False, {"error": f"Unexpected response: {response.status_code}"}
57
+
58
+ except requests.exceptions.ConnectionError:
59
+ return False, {"error": f"Unable to connect to {api_base}. Please check your internet connection."}
60
+ except requests.exceptions.Timeout:
61
+ return False, {"error": "Request timed out. Please try again."}
62
+ except requests.exceptions.RequestException as e:
63
+ return False, {"error": f"Network error: {str(e)}"}
64
+
65
+
66
+ def login(username, password):
67
+ """
68
+ Authenticate with the platform.
69
+
70
+ Returns: (success: bool, result: dict)
71
+ result contains: {"token": "...", "username": "..."} or {"error": "..."}
72
+ """
73
+ return _make_request("POST", "/api/auth/login", data={
74
+ "username": username,
75
+ "password": password
76
+ })
77
+
78
+
79
+ def get_apps(token):
80
+ """
81
+ Get list of all apps.
82
+
83
+ Returns: (success: bool, result: dict)
84
+ result contains: {"apps": [...]} or {"error": "..."}
85
+ """
86
+ return _make_request("GET", "/api/apps", token=token)
87
+
88
+
89
+ def create_app(token, app_name):
90
+ """
91
+ Create a new app.
92
+
93
+ Returns: (success: bool, result: dict)
94
+ result contains: {"app": {...}, "domain": "...", "git_remote": "...", "push_instructions": "..."} or {"error": "..."}
95
+ """
96
+ return _make_request("POST", "/api/apps/create", token=token, data={
97
+ "name": app_name
98
+ })
99
+
100
+
101
+ def scale_app(token, app_name, replicas):
102
+ """
103
+ Scale an app to specified replica count.
104
+
105
+ Returns: (success: bool, result: dict)
106
+ result contains: {"app": {...}, "replicas": ...} or {"error": "..."}
107
+ """
108
+ return _make_request("POST", f"/api/apps/{app_name}/scale", token=token, data={
109
+ "replicas": replicas
110
+ })
111
+
112
+
113
+ def get_logs(token, app_name):
114
+ """
115
+ Get logs for an app.
116
+
117
+ Returns: (success: bool, result: dict)
118
+ result contains: {"app": "...", "logs": "..."} or {"error": "..."}
119
+ """
120
+ return _make_request("GET", f"/api/apps/{app_name}/logs", token=token)
121
+
122
+
123
+ def get_metrics(token, app_name):
124
+ """
125
+ Get metrics for an app.
126
+
127
+ Returns: (success: bool, result: dict)
128
+ result contains: {"app": "...", "replicas": [...]} or {"error": "..."}
129
+ """
130
+ return _make_request("GET", f"/api/apps/{app_name}/metrics", token=token)
drown/cli.py ADDED
@@ -0,0 +1,248 @@
1
+ """CLI commands for Drown Platform."""
2
+
3
+ import click
4
+ import getpass
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from drown import api, config
10
+
11
+
12
+ @click.group()
13
+ @click.version_option(version="0.1.0", prog_name="drown")
14
+ def cli():
15
+ """Drown Platform CLI - Manage your self-hosted PaaS."""
16
+ pass
17
+
18
+
19
+ @cli.command()
20
+ def login():
21
+ """Login to Drown Platform."""
22
+ click.echo("Login to Drown Platform")
23
+ click.echo(f"API: {config.get_api_base()}\n")
24
+
25
+ username = click.prompt("Username")
26
+ password = getpass.getpass("Password: ")
27
+
28
+ click.echo("\nAuthenticating...")
29
+
30
+ success, result = api.login(username, password)
31
+
32
+ if success:
33
+ token = result.get("token")
34
+ username = result.get("username")
35
+
36
+ config.save_config(token, username)
37
+
38
+ click.secho(f"✓ Logged in as {username}", fg="green")
39
+ click.echo(f"Config saved to {config.CONFIG_FILE}")
40
+ else:
41
+ click.secho(f"✗ Login failed: {result.get('error')}", fg="red", err=True)
42
+ sys.exit(1)
43
+
44
+
45
+ @cli.command()
46
+ def logout():
47
+ """Logout and remove saved credentials."""
48
+ if config.delete_config():
49
+ click.secho("✓ Logged out successfully", fg="green")
50
+ click.echo(f"Removed {config.CONFIG_FILE}")
51
+ else:
52
+ click.echo("Not logged in")
53
+
54
+
55
+ @cli.command()
56
+ def apps():
57
+ """List all your apps."""
58
+ token = config.get_token()
59
+ if not token:
60
+ click.secho("✗ Please run 'drown login' first", fg="red", err=True)
61
+ sys.exit(1)
62
+
63
+ success, result = api.get_apps(token)
64
+
65
+ if not success:
66
+ click.secho(f"✗ Error: {result.get('error')}", fg="red", err=True)
67
+ sys.exit(1)
68
+
69
+ apps_list = result.get("apps", [])
70
+
71
+ if not apps_list:
72
+ click.echo("No apps found. Create one with 'drown create <app-name>'")
73
+ return
74
+
75
+ # Print table header
76
+ click.echo(f"{'NAME':<20} {'DOMAIN':<35} {'STATUS':<10} {'REPLICAS':<8}")
77
+ click.echo("-" * 75)
78
+
79
+ # Print each app
80
+ for app in apps_list:
81
+ name = app.get("name", "")
82
+ domain = app.get("domain", "N/A")
83
+ status = app.get("status", "unknown")
84
+ replicas = app.get("replicas", 0)
85
+
86
+ # Color code status
87
+ if status == "running":
88
+ status_colored = click.style(status, fg="green")
89
+ elif status == "stopped":
90
+ status_colored = click.style(status, fg="red")
91
+ else:
92
+ status_colored = status
93
+
94
+ click.echo(f"{name:<20} {domain:<35} {status_colored:<10} {replicas:<8}")
95
+
96
+
97
+ @cli.command()
98
+ @click.argument("app_name")
99
+ def create(app_name):
100
+ """Create a new app."""
101
+ token = config.get_token()
102
+ if not token:
103
+ click.secho("✗ Please run 'drown login' first", fg="red", err=True)
104
+ sys.exit(1)
105
+
106
+ click.echo(f"Creating app '{app_name}'...")
107
+
108
+ success, result = api.create_app(token, app_name)
109
+
110
+ if not success:
111
+ click.secho(f"✗ Error: {result.get('error')}", fg="red", err=True)
112
+ sys.exit(1)
113
+
114
+ domain = result.get("domain", "N/A")
115
+ git_remote = result.get("git_remote")
116
+ push_instructions = result.get("push_instructions", "")
117
+
118
+ click.secho(f"✓ App '{app_name}' created successfully!", fg="green")
119
+ click.echo(f"Domain: {domain}")
120
+
121
+ # Check if we're in a git repo
122
+ git_dir = Path(".git")
123
+ if git_dir.exists() and git_remote:
124
+ click.echo("\nAdding git remote 'platform'...")
125
+
126
+ try:
127
+ # Check if remote already exists
128
+ result = subprocess.run(
129
+ ["git", "remote", "get-url", "platform"],
130
+ capture_output=True,
131
+ text=True
132
+ )
133
+
134
+ if result.returncode == 0:
135
+ click.secho("⚠ Remote 'platform' already exists. Skipping.", fg="yellow")
136
+ else:
137
+ # Add the remote
138
+ subprocess.run(
139
+ ["git", "remote", "add", "platform", git_remote],
140
+ check=True,
141
+ capture_output=True
142
+ )
143
+ click.secho(f"✓ Git remote 'platform' added", fg="green")
144
+ except subprocess.CalledProcessError as e:
145
+ click.secho(f"⚠ Failed to add git remote: {e}", fg="yellow")
146
+ except FileNotFoundError:
147
+ click.secho("⚠ Git not found. Please install git to use auto-remote feature.", fg="yellow")
148
+
149
+ # Print deployment instructions
150
+ if push_instructions:
151
+ click.echo(f"\n{push_instructions}")
152
+
153
+
154
+ @cli.command()
155
+ @click.argument("app_name")
156
+ @click.argument("count", type=int)
157
+ def scale(app_name, count):
158
+ """Scale an app to COUNT replicas."""
159
+ token = config.get_token()
160
+ if not token:
161
+ click.secho("✗ Please run 'drown login' first", fg="red", err=True)
162
+ sys.exit(1)
163
+
164
+ if count < 0:
165
+ click.secho("✗ Replica count must be non-negative", fg="red", err=True)
166
+ sys.exit(1)
167
+
168
+ click.echo(f"Scaling '{app_name}' to {count} replica(s)...")
169
+
170
+ success, result = api.scale_app(token, app_name, count)
171
+
172
+ if not success:
173
+ click.secho(f"✗ Error: {result.get('error')}", fg="red", err=True)
174
+ sys.exit(1)
175
+
176
+ new_count = result.get("replicas", count)
177
+ click.secho(f"✓ Scaled '{app_name}' to {new_count} replica(s)", fg="green")
178
+
179
+
180
+ @cli.command()
181
+ @click.argument("app_name")
182
+ def logs(app_name):
183
+ """View logs for an app."""
184
+ token = config.get_token()
185
+ if not token:
186
+ click.secho("✗ Please run 'drown login' first", fg="red", err=True)
187
+ sys.exit(1)
188
+
189
+ success, result = api.get_logs(token, app_name)
190
+
191
+ if not success:
192
+ click.secho(f"✗ Error: {result.get('error')}", fg="red", err=True)
193
+ sys.exit(1)
194
+
195
+ logs_text = result.get("logs", "")
196
+
197
+ if logs_text:
198
+ click.echo(logs_text)
199
+ else:
200
+ click.echo("No logs available")
201
+
202
+
203
+ @cli.command()
204
+ @click.argument("app_name")
205
+ def metrics(app_name):
206
+ """View resource metrics for an app."""
207
+ token = config.get_token()
208
+ if not token:
209
+ click.secho("✗ Please run 'drown login' first", fg="red", err=True)
210
+ sys.exit(1)
211
+
212
+ success, result = api.get_metrics(token, app_name)
213
+
214
+ if not success:
215
+ click.secho(f"✗ Error: {result.get('error')}", fg="red", err=True)
216
+ sys.exit(1)
217
+
218
+ replicas = result.get("replicas", [])
219
+
220
+ if not replicas:
221
+ click.echo(f"No replicas found for '{app_name}'")
222
+ return
223
+
224
+ # Print table header
225
+ click.echo(f"{'REPLICA':<10} {'PORT':<8} {'STATUS':<10} {'CPU':<10} {'MEMORY':<15}")
226
+ click.echo("-" * 55)
227
+
228
+ # Print each replica
229
+ for replica in replicas:
230
+ replica_num = replica.get("replica_num", "?")
231
+ port = replica.get("port", "N/A")
232
+ status = replica.get("status", "unknown")
233
+ cpu = replica.get("cpu", "N/A")
234
+ memory = replica.get("memory", "N/A")
235
+
236
+ # Color code status
237
+ if status == "running":
238
+ status_colored = click.style(status, fg="green")
239
+ elif status == "stopped":
240
+ status_colored = click.style(status, fg="red")
241
+ else:
242
+ status_colored = status
243
+
244
+ click.echo(f"{replica_num:<10} {port:<8} {status_colored:<10} {cpu:<10} {memory:<15}")
245
+
246
+
247
+ if __name__ == "__main__":
248
+ cli()
drown/config.py ADDED
@@ -0,0 +1,73 @@
1
+ """Configuration management for Drown CLI."""
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+
7
+ # Default API base URL
8
+ DEFAULT_API_BASE = "https://dashboard.dr0wn.duckdns.org"
9
+
10
+ # Config file location
11
+ CONFIG_DIR = Path.home() / ".drown"
12
+ CONFIG_FILE = CONFIG_DIR / "config.json"
13
+
14
+
15
+ def get_api_base():
16
+ """Get API base URL from environment or default."""
17
+ return os.environ.get("DROWN_API_URL", DEFAULT_API_BASE)
18
+
19
+
20
+ def save_config(token, username):
21
+ """Save authentication config to ~/.drown/config.json."""
22
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
23
+
24
+ config = {
25
+ "token": token,
26
+ "username": username,
27
+ "api_base": get_api_base()
28
+ }
29
+
30
+ with open(CONFIG_FILE, "w") as f:
31
+ json.dump(config, f, indent=2)
32
+
33
+ # Set restrictive permissions (Unix-like systems)
34
+ if hasattr(os, 'chmod'):
35
+ os.chmod(CONFIG_FILE, 0o600)
36
+
37
+
38
+ def load_config():
39
+ """Load authentication config from ~/.drown/config.json."""
40
+ if not CONFIG_FILE.exists():
41
+ return None
42
+
43
+ try:
44
+ with open(CONFIG_FILE, "r") as f:
45
+ config = json.load(f)
46
+
47
+ # Ensure api_base is set (for older config files)
48
+ if "api_base" not in config:
49
+ config["api_base"] = get_api_base()
50
+
51
+ return config
52
+ except (json.JSONDecodeError, IOError):
53
+ return None
54
+
55
+
56
+ def delete_config():
57
+ """Delete the config file (logout)."""
58
+ if CONFIG_FILE.exists():
59
+ CONFIG_FILE.unlink()
60
+ return True
61
+ return False
62
+
63
+
64
+ def get_token():
65
+ """Get the saved auth token, or None if not logged in."""
66
+ config = load_config()
67
+ return config["token"] if config else None
68
+
69
+
70
+ def get_username():
71
+ """Get the saved username, or None if not logged in."""
72
+ config = load_config()
73
+ return config["username"] if config else None
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: drown
3
+ Version: 0.1.0
4
+ Summary: CLI tool for managing apps on Drown Platform - a self-hosted PaaS
5
+ Home-page: https://github.com/drownplatform/drown-cli
6
+ Author: Drown Platform
7
+ Author-email: platform@dr0wn.duckdns.org
8
+ Project-URL: Bug Tracker, https://github.com/drownplatform/drown-cli/issues
9
+ Project-URL: Documentation, https://github.com/drownplatform/drown-cli#readme
10
+ Project-URL: Source Code, https://github.com/drownplatform/drown-cli
11
+ Keywords: drown platform paas cli deployment docker heroku
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Topic :: Software Development :: Build Tools
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.7
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.0.0
29
+ Requires-Dist: requests>=2.25.0
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: keywords
37
+ Dynamic: license-file
38
+ Dynamic: project-url
39
+ Dynamic: requires-dist
40
+ Dynamic: requires-python
41
+ Dynamic: summary
42
+
43
+ # drown-cli
44
+
45
+ **CLI tool for managing apps on Drown Platform** - a self-hosted PaaS (Platform as a Service).
46
+
47
+ Deploy and manage containerized applications with simple commands, similar to Heroku CLI but for your own infrastructure.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install drown-cli
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```bash
58
+ # Login to your Drown Platform instance
59
+ drown login
60
+
61
+ # List your apps
62
+ drown apps
63
+
64
+ # Create a new app
65
+ drown create my-app
66
+
67
+ # Scale your app
68
+ drown scale my-app 3
69
+
70
+ # View logs
71
+ drown logs my-app
72
+
73
+ # Check metrics
74
+ drown metrics my-app
75
+
76
+ # Logout
77
+ drown logout
78
+ ```
79
+
80
+ ## Commands
81
+
82
+ ### `drown login`
83
+ Authenticate with your Drown Platform instance. You'll be prompted for your username and password.
84
+
85
+ ```bash
86
+ drown login
87
+ ```
88
+
89
+ Credentials are saved to `~/.drown/config.json` for subsequent commands.
90
+
91
+ ### `drown logout`
92
+ Remove saved credentials.
93
+
94
+ ```bash
95
+ drown logout
96
+ ```
97
+
98
+ ### `drown apps`
99
+ List all your deployed applications.
100
+
101
+ ```bash
102
+ drown apps
103
+ ```
104
+
105
+ Output:
106
+ ```
107
+ NAME DOMAIN STATUS REPLICAS
108
+ my-app my-app.dr0wn.duckdns.org running 3
109
+ web-frontend web.dr0wn.duckdns.org running 1
110
+ ```
111
+
112
+ ### `drown create <app-name>`
113
+ Create a new application.
114
+
115
+ ```bash
116
+ drown create my-new-app
117
+ ```
118
+
119
+ If run from a git repository, automatically adds a `platform` git remote. Then deploy with:
120
+ ```bash
121
+ git push platform main
122
+ ```
123
+
124
+ ### `drown scale <app-name> <count>`
125
+ Scale the number of replicas for an application.
126
+
127
+ ```bash
128
+ drown scale my-app 5
129
+ ```
130
+
131
+ ### `drown logs <app-name>`
132
+ View application logs.
133
+
134
+ ```bash
135
+ drown logs my-app
136
+ ```
137
+
138
+ ### `drown metrics <app-name>`
139
+ View resource usage metrics (CPU, memory) for all replicas.
140
+
141
+ ```bash
142
+ drown metrics my-app
143
+ ```
144
+
145
+ Output:
146
+ ```
147
+ REPLICA PORT STATUS CPU MEMORY
148
+ 0 8001 running 2.1% 64MiB
149
+ 1 8002 running 1.8% 62MiB
150
+ 2 8003 running 2.4% 65MiB
151
+ ```
152
+
153
+ ## Configuration
154
+
155
+ ### API URL
156
+ By default, the CLI connects to `https://dashboard.dr0wn.duckdns.org`.
157
+
158
+ To use a custom instance, set the `DROWN_API_URL` environment variable:
159
+
160
+ ```bash
161
+ export DROWN_API_URL=https://your-instance.com
162
+ drown login
163
+ ```
164
+
165
+ ### Config File
166
+ Authentication token is stored in `~/.drown/config.json`:
167
+
168
+ ```json
169
+ {
170
+ "token": "your-jwt-token",
171
+ "username": "your-username",
172
+ "api_base": "https://dashboard.dr0wn.duckdns.org"
173
+ }
174
+ ```
175
+
176
+ ## Requirements
177
+
178
+ - Python 3.7+
179
+ - Git (optional, for `drown create` auto-remote feature)
180
+
181
+ ## Development
182
+
183
+ ```bash
184
+ git clone https://github.com/drownplatform/drown-cli.git
185
+ cd drown-cli
186
+ pip install -e .
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT License - see [LICENSE](LICENSE) file for details.
192
+
193
+ ## Links
194
+
195
+ - **Platform**: [dashboard.dr0wn.duckdns.org](https://dashboard.dr0wn.duckdns.org)
196
+ - **GitHub**: [github.com/drownplatform/drown-cli](https://github.com/drownplatform/drown-cli)
197
+ - **PyPI**: [pypi.org/project/drown-cli](https://pypi.org/project/drown-cli)
198
+
199
+ ## Support
200
+
201
+ For issues or questions, please open an issue on [GitHub](https://github.com/drownplatform/drown-cli/issues).
@@ -0,0 +1,10 @@
1
+ drown/__init__.py,sha256=qBHx3QDcl9BpBpGctB7yS4K_99ACSpw6WvO7aUJAfAY,105
2
+ drown/api.py,sha256=BP9UNDmL6rr0_DN3vs0mvP6NoX_iGeLK_WQ9aiIqwj0,4451
3
+ drown/cli.py,sha256=NH70qq_gL2W_8Q2J1lsHw00jISCb9DST44hiIcdLsbU,7630
4
+ drown/config.py,sha256=s9SyE5BhwEFgUH-f9O1hY9i0a0Uf8GXPCB7jaQ9pQqY,1908
5
+ drown-0.1.0.dist-info/licenses/LICENSE,sha256=M-GqxSGf3Qks92eNPFeqLy18FhJse78A7UIAYJoFMpo,1092
6
+ drown-0.1.0.dist-info/METADATA,sha256=i2SHYnkQgovrLvVresaOFG0w_Mkr1V_h18GDITpJVqo,4747
7
+ drown-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
8
+ drown-0.1.0.dist-info/entry_points.txt,sha256=z62wKm7hcB1JdJu9HqyYa1PV5sfqi3LS-YiiIqLXsDU,40
9
+ drown-0.1.0.dist-info/top_level.txt,sha256=y-SfXlwaR0r6aYiosjX1R7de68_d2IyQuTSlBl989cE,6
10
+ drown-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ drown = drown.cli:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Drown Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ drown