waitfor-cli 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.
- waitfor/__init__.py +3 -0
- waitfor/cli.py +289 -0
- waitfor_cli-0.1.0.dist-info/METADATA +176 -0
- waitfor_cli-0.1.0.dist-info/RECORD +8 -0
- waitfor_cli-0.1.0.dist-info/WHEEL +5 -0
- waitfor_cli-0.1.0.dist-info/entry_points.txt +2 -0
- waitfor_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- waitfor_cli-0.1.0.dist-info/top_level.txt +1 -0
waitfor/__init__.py
ADDED
waitfor/cli.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""waitfor - Wait for conditions (port, file, URL, process) in automation scripts."""
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import socket
|
|
6
|
+
import time
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Callable, Any, Dict
|
|
13
|
+
from urllib.request import urlopen, Request
|
|
14
|
+
from urllib.error import URLError, HTTPError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def wait_loop(
|
|
18
|
+
check_fn: Callable[[], bool],
|
|
19
|
+
condition_name: str,
|
|
20
|
+
timeout: int,
|
|
21
|
+
interval: float,
|
|
22
|
+
quiet: bool,
|
|
23
|
+
json_output: bool,
|
|
24
|
+
invert: bool = False
|
|
25
|
+
) -> Dict[str, Any]:
|
|
26
|
+
"""Generic wait loop. Returns result dict."""
|
|
27
|
+
start = time.time()
|
|
28
|
+
attempts = 0
|
|
29
|
+
|
|
30
|
+
while True:
|
|
31
|
+
attempts += 1
|
|
32
|
+
elapsed = time.time() - start
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
result = check_fn()
|
|
36
|
+
# If invert, we want the check to be False
|
|
37
|
+
success = (not result) if invert else result
|
|
38
|
+
except Exception as e:
|
|
39
|
+
if not quiet and not json_output:
|
|
40
|
+
click.echo(f" Check error: {e}", err=True)
|
|
41
|
+
success = invert # Error counts as False, which is success if inverted
|
|
42
|
+
|
|
43
|
+
if success:
|
|
44
|
+
result_data = {
|
|
45
|
+
"success": True,
|
|
46
|
+
"condition": condition_name,
|
|
47
|
+
"elapsed_seconds": round(elapsed, 2),
|
|
48
|
+
"attempts": attempts
|
|
49
|
+
}
|
|
50
|
+
if json_output:
|
|
51
|
+
click.echo(json.dumps(result_data))
|
|
52
|
+
elif not quiet:
|
|
53
|
+
click.echo(f"✓ {condition_name} (took {elapsed:.1f}s, {attempts} attempts)")
|
|
54
|
+
return result_data
|
|
55
|
+
|
|
56
|
+
if timeout > 0 and elapsed >= timeout:
|
|
57
|
+
result_data = {
|
|
58
|
+
"success": False,
|
|
59
|
+
"condition": condition_name,
|
|
60
|
+
"elapsed_seconds": round(elapsed, 2),
|
|
61
|
+
"attempts": attempts,
|
|
62
|
+
"error": f"Timeout after {timeout}s"
|
|
63
|
+
}
|
|
64
|
+
if json_output:
|
|
65
|
+
click.echo(json.dumps(result_data))
|
|
66
|
+
elif not quiet:
|
|
67
|
+
click.echo(f"✗ Timeout waiting for {condition_name}", err=True)
|
|
68
|
+
return result_data
|
|
69
|
+
|
|
70
|
+
if not quiet and not json_output:
|
|
71
|
+
click.echo(f" Waiting for {condition_name}... ({elapsed:.0f}s)", err=True)
|
|
72
|
+
|
|
73
|
+
time.sleep(interval)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@click.group()
|
|
77
|
+
@click.version_option()
|
|
78
|
+
def cli():
|
|
79
|
+
"""Wait for conditions in automation scripts.
|
|
80
|
+
|
|
81
|
+
Useful for waiting on services, files, or processes before continuing.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
|
|
85
|
+
waitfor port 8080
|
|
86
|
+
|
|
87
|
+
waitfor file /tmp/ready.flag
|
|
88
|
+
|
|
89
|
+
waitfor url http://localhost:3000/health
|
|
90
|
+
|
|
91
|
+
waitfor process nginx
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@cli.command("port")
|
|
97
|
+
@click.argument("port", type=int)
|
|
98
|
+
@click.option("-h", "--host", default="localhost", help="Host to check")
|
|
99
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
100
|
+
@click.option("-i", "--interval", default=1.0, help="Check interval in seconds")
|
|
101
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
102
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
103
|
+
@click.option("--closed", is_flag=True, help="Wait for port to be CLOSED instead")
|
|
104
|
+
def wait_port(port: int, host: str, timeout: int, interval: float, quiet: bool, json_output: bool, closed: bool):
|
|
105
|
+
"""Wait for a TCP port to be open (or closed with --closed)."""
|
|
106
|
+
|
|
107
|
+
def check_port():
|
|
108
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
109
|
+
sock.settimeout(1)
|
|
110
|
+
try:
|
|
111
|
+
result = sock.connect_ex((host, port))
|
|
112
|
+
return result == 0
|
|
113
|
+
finally:
|
|
114
|
+
sock.close()
|
|
115
|
+
|
|
116
|
+
action = "closed" if closed else "open"
|
|
117
|
+
condition = f"port {port} on {host} to be {action}"
|
|
118
|
+
result = wait_loop(check_port, condition, timeout, interval, quiet, json_output, invert=closed)
|
|
119
|
+
sys.exit(0 if result["success"] else 1)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@cli.command("file")
|
|
123
|
+
@click.argument("path")
|
|
124
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
125
|
+
@click.option("-i", "--interval", default=1.0, help="Check interval in seconds")
|
|
126
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
127
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
128
|
+
@click.option("--gone", is_flag=True, help="Wait for file to be GONE instead")
|
|
129
|
+
@click.option("--contains", "content", help="Wait for file to contain this string")
|
|
130
|
+
def wait_file(path: str, timeout: int, interval: float, quiet: bool, json_output: bool, gone: bool, content: Optional[str]):
|
|
131
|
+
"""Wait for a file to exist (or be gone with --gone)."""
|
|
132
|
+
|
|
133
|
+
def check_file():
|
|
134
|
+
p = Path(path)
|
|
135
|
+
if not p.exists():
|
|
136
|
+
return False
|
|
137
|
+
if content:
|
|
138
|
+
try:
|
|
139
|
+
return content in p.read_text()
|
|
140
|
+
except:
|
|
141
|
+
return False
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
if content:
|
|
145
|
+
condition = f"file {path} to contain '{content}'"
|
|
146
|
+
elif gone:
|
|
147
|
+
condition = f"file {path} to be gone"
|
|
148
|
+
else:
|
|
149
|
+
condition = f"file {path} to exist"
|
|
150
|
+
|
|
151
|
+
result = wait_loop(check_file, condition, timeout, interval, quiet, json_output, invert=gone)
|
|
152
|
+
sys.exit(0 if result["success"] else 1)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@cli.command("url")
|
|
156
|
+
@click.argument("url")
|
|
157
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
158
|
+
@click.option("-i", "--interval", default=2.0, help="Check interval in seconds")
|
|
159
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
160
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
161
|
+
@click.option("--status", type=int, help="Expected HTTP status code (default: any 2xx)")
|
|
162
|
+
@click.option("--contains", "content", help="Response must contain this string")
|
|
163
|
+
def wait_url(url: str, timeout: int, interval: float, quiet: bool, json_output: bool, status: Optional[int], content: Optional[str]):
|
|
164
|
+
"""Wait for a URL to respond successfully."""
|
|
165
|
+
|
|
166
|
+
def check_url():
|
|
167
|
+
try:
|
|
168
|
+
req = Request(url, headers={"User-Agent": "waitfor/1.0"})
|
|
169
|
+
resp = urlopen(req, timeout=5)
|
|
170
|
+
code = resp.getcode()
|
|
171
|
+
|
|
172
|
+
if status and code != status:
|
|
173
|
+
return False
|
|
174
|
+
elif not status and not (200 <= code < 300):
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
if content:
|
|
178
|
+
body = resp.read().decode("utf-8", errors="ignore")
|
|
179
|
+
return content in body
|
|
180
|
+
|
|
181
|
+
return True
|
|
182
|
+
except (URLError, HTTPError):
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
condition = f"URL {url} to respond"
|
|
186
|
+
if status:
|
|
187
|
+
condition += f" with status {status}"
|
|
188
|
+
if content:
|
|
189
|
+
condition += f" containing '{content}'"
|
|
190
|
+
|
|
191
|
+
result = wait_loop(check_url, condition, timeout, interval, quiet, json_output)
|
|
192
|
+
sys.exit(0 if result["success"] else 1)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@cli.command("process")
|
|
196
|
+
@click.argument("name")
|
|
197
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
198
|
+
@click.option("-i", "--interval", default=1.0, help="Check interval in seconds")
|
|
199
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
200
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
201
|
+
@click.option("--gone", is_flag=True, help="Wait for process to be GONE instead")
|
|
202
|
+
def wait_process(name: str, timeout: int, interval: float, quiet: bool, json_output: bool, gone: bool):
|
|
203
|
+
"""Wait for a process to be running (or gone with --gone)."""
|
|
204
|
+
|
|
205
|
+
def check_process():
|
|
206
|
+
try:
|
|
207
|
+
# Use pgrep for cross-platform process detection
|
|
208
|
+
result = subprocess.run(
|
|
209
|
+
["pgrep", "-x", name],
|
|
210
|
+
capture_output=True,
|
|
211
|
+
timeout=5
|
|
212
|
+
)
|
|
213
|
+
return result.returncode == 0
|
|
214
|
+
except:
|
|
215
|
+
# Fallback: check ps output
|
|
216
|
+
try:
|
|
217
|
+
result = subprocess.run(
|
|
218
|
+
["ps", "aux"],
|
|
219
|
+
capture_output=True,
|
|
220
|
+
text=True,
|
|
221
|
+
timeout=5
|
|
222
|
+
)
|
|
223
|
+
return name in result.stdout
|
|
224
|
+
except:
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
action = "to stop" if gone else "to start"
|
|
228
|
+
condition = f"process '{name}' {action}"
|
|
229
|
+
result = wait_loop(check_process, condition, timeout, interval, quiet, json_output, invert=gone)
|
|
230
|
+
sys.exit(0 if result["success"] else 1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@cli.command("command")
|
|
234
|
+
@click.argument("cmd")
|
|
235
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
236
|
+
@click.option("-i", "--interval", default=2.0, help="Check interval in seconds")
|
|
237
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
238
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
239
|
+
@click.option("--shell/--no-shell", default=True, help="Run through shell")
|
|
240
|
+
def wait_command(cmd: str, timeout: int, interval: float, quiet: bool, json_output: bool, shell: bool):
|
|
241
|
+
"""Wait for a command to exit with status 0."""
|
|
242
|
+
|
|
243
|
+
def check_command():
|
|
244
|
+
try:
|
|
245
|
+
result = subprocess.run(
|
|
246
|
+
cmd,
|
|
247
|
+
shell=shell,
|
|
248
|
+
capture_output=True,
|
|
249
|
+
timeout=10
|
|
250
|
+
)
|
|
251
|
+
return result.returncode == 0
|
|
252
|
+
except:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
condition = f"command '{cmd}' to succeed"
|
|
256
|
+
result = wait_loop(check_command, condition, timeout, interval, quiet, json_output)
|
|
257
|
+
sys.exit(0 if result["success"] else 1)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@cli.command("tcp")
|
|
261
|
+
@click.argument("host")
|
|
262
|
+
@click.argument("port", type=int)
|
|
263
|
+
@click.option("-t", "--timeout", default=30, help="Timeout in seconds (0=forever)")
|
|
264
|
+
@click.option("-i", "--interval", default=1.0, help="Check interval in seconds")
|
|
265
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress output")
|
|
266
|
+
@click.option("--json", "json_output", is_flag=True, help="JSON output")
|
|
267
|
+
def wait_tcp(host: str, port: int, timeout: int, interval: float, quiet: bool, json_output: bool):
|
|
268
|
+
"""Wait for a remote TCP connection (host + port)."""
|
|
269
|
+
|
|
270
|
+
def check_tcp():
|
|
271
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
272
|
+
sock.settimeout(2)
|
|
273
|
+
try:
|
|
274
|
+
result = sock.connect_ex((host, port))
|
|
275
|
+
return result == 0
|
|
276
|
+
finally:
|
|
277
|
+
sock.close()
|
|
278
|
+
|
|
279
|
+
condition = f"TCP connection to {host}:{port}"
|
|
280
|
+
result = wait_loop(check_tcp, condition, timeout, interval, quiet, json_output)
|
|
281
|
+
sys.exit(0 if result["success"] else 1)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def main():
|
|
285
|
+
cli()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waitfor-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wait for conditions (port, file, URL, process) in automation scripts
|
|
5
|
+
Author-email: Marcus <marcus.builds.things@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/marcusbuildsthings-droid/waitfor
|
|
8
|
+
Project-URL: Repository, https://github.com/marcusbuildsthings-droid/waitfor
|
|
9
|
+
Project-URL: Issues, https://github.com/marcusbuildsthings-droid/waitfor/issues
|
|
10
|
+
Keywords: cli,automation,wait,port,process,devops,scripting
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
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.8
|
|
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 :: System :: Systems Administration
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: click>=8.0
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# waitfor
|
|
32
|
+
|
|
33
|
+
Wait for conditions in automation scripts. Check for ports, files, URLs, or processes before continuing.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install waitfor-cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Wait for a port to be open
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Wait for port 8080 on localhost
|
|
47
|
+
waitfor port 8080
|
|
48
|
+
|
|
49
|
+
# Wait for port on remote host
|
|
50
|
+
waitfor port 5432 -h db.example.com
|
|
51
|
+
|
|
52
|
+
# Wait for port to be CLOSED
|
|
53
|
+
waitfor port 8080 --closed
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Wait for a file
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Wait for file to exist
|
|
60
|
+
waitfor file /tmp/ready.flag
|
|
61
|
+
|
|
62
|
+
# Wait for file to be gone
|
|
63
|
+
waitfor file /var/run/app.pid --gone
|
|
64
|
+
|
|
65
|
+
# Wait for file to contain specific content
|
|
66
|
+
waitfor file /var/log/app.log --contains "Server started"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Wait for a URL to respond
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Wait for health endpoint
|
|
73
|
+
waitfor url http://localhost:3000/health
|
|
74
|
+
|
|
75
|
+
# Wait for specific status code
|
|
76
|
+
waitfor url http://api.example.com/status --status 200
|
|
77
|
+
|
|
78
|
+
# Wait for response to contain string
|
|
79
|
+
waitfor url http://localhost:8080/ready --contains "ok"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Wait for a process
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Wait for process to start
|
|
86
|
+
waitfor process nginx
|
|
87
|
+
|
|
88
|
+
# Wait for process to stop
|
|
89
|
+
waitfor process nginx --gone
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Wait for a command to succeed
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Wait for docker container to be healthy
|
|
96
|
+
waitfor command "docker inspect --format='{{.State.Health.Status}}' myapp | grep healthy"
|
|
97
|
+
|
|
98
|
+
# Wait for database to accept connections
|
|
99
|
+
waitfor command "pg_isready -h localhost"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Wait for remote TCP connection
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Wait for database to be reachable
|
|
106
|
+
waitfor tcp db.example.com 5432
|
|
107
|
+
|
|
108
|
+
# Wait for Redis
|
|
109
|
+
waitfor tcp localhost 6379
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Options
|
|
113
|
+
|
|
114
|
+
All commands support these common options:
|
|
115
|
+
|
|
116
|
+
| Option | Description | Default |
|
|
117
|
+
|--------|-------------|---------|
|
|
118
|
+
| `-t, --timeout` | Timeout in seconds (0 = forever) | 30 |
|
|
119
|
+
| `-i, --interval` | Check interval in seconds | 1.0-2.0 |
|
|
120
|
+
| `-q, --quiet` | Suppress output | off |
|
|
121
|
+
| `--json` | JSON output | off |
|
|
122
|
+
|
|
123
|
+
## Exit Codes
|
|
124
|
+
|
|
125
|
+
- `0` - Condition met
|
|
126
|
+
- `1` - Timeout or condition not met
|
|
127
|
+
|
|
128
|
+
## JSON Output
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
$ waitfor port 8080 --json
|
|
132
|
+
{"success": true, "condition": "port 8080 on localhost to be open", "elapsed_seconds": 0.01, "attempts": 1}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Examples
|
|
136
|
+
|
|
137
|
+
### Wait for services before running tests
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
#!/bin/bash
|
|
141
|
+
waitfor port 5432 -t 60 && \
|
|
142
|
+
waitfor port 6379 -t 30 && \
|
|
143
|
+
waitfor url http://localhost:8080/health -t 60 && \
|
|
144
|
+
pytest
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Deployment script
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
#!/bin/bash
|
|
151
|
+
docker-compose up -d
|
|
152
|
+
|
|
153
|
+
# Wait for all services
|
|
154
|
+
waitfor port 80 -t 120
|
|
155
|
+
waitfor url http://localhost/api/health --contains "healthy" -t 60
|
|
156
|
+
|
|
157
|
+
echo "Deployment complete!"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Wait for build artifact
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Start build in background
|
|
164
|
+
make build &
|
|
165
|
+
|
|
166
|
+
# Wait for output
|
|
167
|
+
waitfor file ./dist/app.js -t 300
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## For AI Agents
|
|
171
|
+
|
|
172
|
+
See [SKILL.md](SKILL.md) for agent-optimized documentation.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
waitfor/__init__.py,sha256=Ck2AXo82-T6hZI5nIR9HGIBfXAT-iULduLwyHotaV90,86
|
|
2
|
+
waitfor/cli.py,sha256=9swfOCo-KnotMofHz2JlegTy8A2HDJVzA01hnJjA18k,10447
|
|
3
|
+
waitfor_cli-0.1.0.dist-info/licenses/LICENSE,sha256=9tNBpWq8KGbuJqmeComp40OiNnbvpvsKn1YP26PUtck,1063
|
|
4
|
+
waitfor_cli-0.1.0.dist-info/METADATA,sha256=zeagwZc_fyKSpJCaM8BzfpWRPXpU8e4O2yCUbIyOlug,4015
|
|
5
|
+
waitfor_cli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
waitfor_cli-0.1.0.dist-info/entry_points.txt,sha256=UB6yg40JW5YcC8VtL8Ucqq0LOuvBEM6AUVZ4L6TAbqs,45
|
|
7
|
+
waitfor_cli-0.1.0.dist-info/top_level.txt,sha256=CZo7W0FVVs1rRLRxwZNeooeRORiEHQdz21OYVUoMpWI,8
|
|
8
|
+
waitfor_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcus
|
|
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
|
+
waitfor
|