wellspin 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,4 @@
1
+ node_modules/
2
+ test/.fixtures/
3
+ *.tgz
4
+ .DS_Store
wellspin-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wellspin LLC, Yevhen Reuka and Dmytro Kazarov
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,89 @@
1
+ Metadata-Version: 2.5
2
+ Name: wellspin
3
+ Version: 0.1.0
4
+ Summary: Prints https://cal.com/wellspin/30min?layout=mobile and opens it in your default browser.
5
+ Author: Wellspin LLC
6
+ Maintainer: Yevhen Reuka, Dmytro Kazarov
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Wellspin LLC, Yevhen Reuka and Dmytro Kazarov
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: browser,cli,link,open
30
+ Classifier: Environment :: Console
31
+ Classifier: Intended Audience :: End Users/Desktop
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Topic :: Utilities
35
+ Requires-Python: >=3.9
36
+ Description-Content-Type: text/markdown
37
+
38
+ # wellspin
39
+
40
+ Prints a link and opens it in your default browser. That is the whole program.
41
+
42
+ uvx wellspin
43
+
44
+ It writes the URL to stdout and asks the operating system to open it. Whether
45
+ the page then loads is between your browser and the network — this command
46
+ cannot see that and does not claim it.
47
+
48
+ ## The link
49
+
50
+ https://cal.com/wellspin/30min?layout=mobile
51
+
52
+ ## Options
53
+
54
+ | Command | What it does |
55
+ | --- | --- |
56
+ | `uvx wellspin` | print the URL and open it |
57
+ | `uvx wellspin --url` | print the URL only, open nothing |
58
+ | `uvx wellspin --help` | usage |
59
+ | `uvx wellspin --version` | the version of this package |
60
+
61
+ `pipx run wellspin` behaves the same way, and `pip install wellspin`
62
+ puts `wellspin` on your PATH if you would rather keep it.
63
+
64
+ In CI the URL is printed and no browser is launched.
65
+
66
+ ## Getting the current link
67
+
68
+ The URL is baked in when the package is published, so a copy already on your
69
+ machine keeps whatever link it shipped with. To be sure you have the newest:
70
+
71
+ uvx wellspin@latest
72
+
73
+ A changed link means a new version of this package.
74
+
75
+ ## Exit codes
76
+
77
+ | Code | Meaning |
78
+ | --- | --- |
79
+ | 0 | the URL was printed — including when the browser could not be launched |
80
+ | 1 | this package has no usable URL configured |
81
+ | 2 | bad arguments |
82
+
83
+ ## Dependencies
84
+
85
+ None. `webbrowser` is part of the Python standard library.
86
+
87
+ ## Licence
88
+
89
+ MIT.
@@ -0,0 +1,52 @@
1
+ # wellspin
2
+
3
+ Prints a link and opens it in your default browser. That is the whole program.
4
+
5
+ uvx wellspin
6
+
7
+ It writes the URL to stdout and asks the operating system to open it. Whether
8
+ the page then loads is between your browser and the network — this command
9
+ cannot see that and does not claim it.
10
+
11
+ ## The link
12
+
13
+ https://cal.com/wellspin/30min?layout=mobile
14
+
15
+ ## Options
16
+
17
+ | Command | What it does |
18
+ | --- | --- |
19
+ | `uvx wellspin` | print the URL and open it |
20
+ | `uvx wellspin --url` | print the URL only, open nothing |
21
+ | `uvx wellspin --help` | usage |
22
+ | `uvx wellspin --version` | the version of this package |
23
+
24
+ `pipx run wellspin` behaves the same way, and `pip install wellspin`
25
+ puts `wellspin` on your PATH if you would rather keep it.
26
+
27
+ In CI the URL is printed and no browser is launched.
28
+
29
+ ## Getting the current link
30
+
31
+ The URL is baked in when the package is published, so a copy already on your
32
+ machine keeps whatever link it shipped with. To be sure you have the newest:
33
+
34
+ uvx wellspin@latest
35
+
36
+ A changed link means a new version of this package.
37
+
38
+ ## Exit codes
39
+
40
+ | Code | Meaning |
41
+ | --- | --- |
42
+ | 0 | the URL was printed — including when the browser could not be launched |
43
+ | 1 | this package has no usable URL configured |
44
+ | 2 | bad arguments |
45
+
46
+ ## Dependencies
47
+
48
+ None. `webbrowser` is part of the Python standard library.
49
+
50
+ ## Licence
51
+
52
+ MIT.
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "wellspin"
7
+ version = "0.1.0"
8
+ description = "Prints https://cal.com/wellspin/30min?layout=mobile and opens it in your default browser."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ keywords = ["cli", "open", "browser", "link"]
13
+ authors = [{ name = "Wellspin LLC" }]
14
+ maintainers = [{ name = "Yevhen Reuka" }, { name = "Dmytro Kazarov" }]
15
+ classifiers = [
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Utilities",
21
+ ]
22
+ dependencies = []
23
+
24
+ [project.scripts]
25
+ wellspin = "wellspin:main"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["wellspin"]
29
+
30
+ [tool.hatch.build.targets.sdist]
31
+ include = ["wellspin", "README.md", "LICENSE"]
@@ -0,0 +1,27 @@
1
+ """wellspin — print a link and open it in your default browser."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version as _version
4
+
5
+ from ._cli import main as _main
6
+
7
+ NAME = "wellspin"
8
+ URL = "https://cal.com/wellspin/30min?layout=mobile"
9
+
10
+
11
+ def _installed_version() -> str:
12
+ try:
13
+ return _version(NAME)
14
+ except PackageNotFoundError: # running from a checkout rather than an install
15
+ return "0+unknown"
16
+
17
+
18
+ def main() -> int:
19
+ """Console entry point. Returns the exit code."""
20
+ return _main(NAME, URL, _installed_version())
21
+
22
+
23
+ def _console() -> None:
24
+ raise SystemExit(main())
25
+
26
+
27
+ __all__ = ["NAME", "URL", "main"]
@@ -0,0 +1,214 @@
1
+ """Shared behaviour for every one of these commands.
2
+
3
+ A deliberate mirror of src/run.js — same flags, same exit codes, same refusal
4
+ to claim more than it knows. Kept free of I/O so the tests can drive it without
5
+ opening tabs, and copied verbatim into each published package.
6
+
7
+ Nothing outside the standard library is used: webbrowser ships with Python, so
8
+ these packages have no dependencies at all.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ import webbrowser
16
+ from typing import Callable, Iterable, Optional, Sequence, Tuple
17
+ from urllib.parse import urlsplit
18
+
19
+ EXIT_OK = 0
20
+ EXIT_CONFIG = 1
21
+ EXIT_USAGE = 2
22
+
23
+ # Values that mean "nobody filled this in yet". Rejected rather than quietly
24
+ # replaced with some default page.
25
+ _PLACEHOLDERS = {
26
+ "none",
27
+ "null",
28
+ "tbd",
29
+ "todo",
30
+ "placeholder",
31
+ "changeme",
32
+ "change-me",
33
+ "replaceme",
34
+ "replace-me",
35
+ "http://",
36
+ "https://",
37
+ }
38
+
39
+ _CI_SIGNALS = (
40
+ "CI",
41
+ "CONTINUOUS_INTEGRATION",
42
+ "GITHUB_ACTIONS",
43
+ "GITLAB_CI",
44
+ "CIRCLECI",
45
+ "TRAVIS",
46
+ "BUILDKITE",
47
+ "DRONE",
48
+ "APPVEYOR",
49
+ "TEAMCITY_VERSION",
50
+ "TF_BUILD",
51
+ "JENKINS_URL",
52
+ "CODEBUILD_BUILD_ID",
53
+ "NETLIFY",
54
+ "VERCEL",
55
+ )
56
+
57
+ _KNOWN_FLAGS = ("--help", "--version", "--url")
58
+
59
+
60
+ def detect_ci(env) -> Optional[str]:
61
+ """Name the variable that identified a CI runner, or None.
62
+
63
+ CI=false and CI=0 count as "not CI" — some shells export it blank.
64
+ """
65
+ for key in _CI_SIGNALS:
66
+ value = env.get(key)
67
+ if value is None:
68
+ continue
69
+ text = str(value).strip().lower()
70
+ if text in ("", "false", "0"):
71
+ continue
72
+ return key
73
+ return None
74
+
75
+
76
+ def read_url(raw) -> Tuple[bool, str]:
77
+ """Check a configured URL without rewriting it.
78
+
79
+ Returns (ok, url_or_reason). The URL comes back trimmed but otherwise
80
+ exactly as configured, so path, query and fragment survive untouched.
81
+ """
82
+ if raw is None:
83
+ return False, "no URL is configured for this package"
84
+ if not isinstance(raw, str):
85
+ return False, "URL must be a string, got %s" % type(raw).__name__
86
+
87
+ url = raw.strip()
88
+ if not url:
89
+ return False, "the configured URL is empty"
90
+ if url.lower() in _PLACEHOLDERS or "<" in url or ">" in url:
91
+ return False, "the configured URL is still a placeholder: %s" % url
92
+
93
+ # urlsplit rather than a hand-rolled pattern: an earlier regex here left
94
+ # "@" out of the host class, so a link carrying credentials fell through to
95
+ # the generic "not a valid URL" message instead of the specific one. The
96
+ # standard parser knows the grammar; matching it by hand does not.
97
+ try:
98
+ parts = urlsplit(url)
99
+ except ValueError as error:
100
+ return False, "the configured URL is not a valid URL: %s (%s)" % (url, error)
101
+
102
+ if parts.scheme != "https":
103
+ return False, "the URL must use https: %s" % url
104
+ try:
105
+ if parts.username or parts.password:
106
+ return False, "the URL must not carry credentials"
107
+ host = parts.hostname
108
+ except ValueError as error:
109
+ return False, "the configured URL is not a valid URL: %s (%s)" % (url, error)
110
+
111
+ if not host:
112
+ return False, "the URL has no host: %s" % url
113
+
114
+ return True, url
115
+
116
+
117
+ def help_text(name: str) -> str:
118
+ return "\n".join(
119
+ (
120
+ "%s — print a link and open it in your default browser." % name,
121
+ "",
122
+ "Usage",
123
+ " uvx %s print the URL and open it" % name,
124
+ " uvx %s --url print the URL only" % name,
125
+ " uvx %s --help show this help" % name,
126
+ " uvx %s --version show the version of this package" % name,
127
+ "",
128
+ "pipx run %s works the same way." % name,
129
+ "",
130
+ "The URL is fixed when the package is published. To be sure you have the",
131
+ "newest one, ask for it explicitly: uvx %s@latest" % name,
132
+ )
133
+ )
134
+
135
+
136
+ def run(
137
+ argv: Sequence[str],
138
+ name: str,
139
+ version: str,
140
+ url,
141
+ open_url: Callable[[str], bool],
142
+ stdout: Callable[[str], None],
143
+ stderr: Callable[[str], None],
144
+ env,
145
+ ) -> int:
146
+ """The whole program. Returns the process exit code."""
147
+ unknown: Iterable[str] = [a for a in argv if a not in _KNOWN_FLAGS]
148
+ unknown = list(unknown)
149
+ if unknown:
150
+ stderr("%s: unknown argument: %s\nTry: %s --help\n" % (name, unknown[0], name))
151
+ return EXIT_USAGE
152
+
153
+ # One thing at a time: --url --version has no single sensible answer.
154
+ flags = list(dict.fromkeys(argv))
155
+ if len(flags) > 1:
156
+ stderr("%s: use one option at a time, got %s\nTry: %s --help\n" % (name, " ".join(flags), name))
157
+ return EXIT_USAGE
158
+
159
+ flag = flags[0] if flags else None
160
+
161
+ # Both answer before the URL is looked at, so they still work on a package
162
+ # whose link has not been filled in yet.
163
+ if flag == "--help":
164
+ stdout(help_text(name) + "\n")
165
+ return EXIT_OK
166
+ if flag == "--version":
167
+ stdout("%s\n" % version)
168
+ return EXIT_OK
169
+
170
+ ok, value = read_url(url)
171
+ if not ok:
172
+ stderr("%s: %s\n" % (name, value))
173
+ return EXIT_CONFIG
174
+
175
+ if flag == "--url":
176
+ stdout("%s\n" % value)
177
+ return EXIT_OK
178
+
179
+ # The link reaches the user first, so it is theirs whatever happens next.
180
+ stdout("%s\n" % value)
181
+
182
+ ci = detect_ci(env)
183
+ if ci:
184
+ stderr("%s: %s is set, not opening a browser.\n" % (name, ci))
185
+ return EXIT_OK
186
+
187
+ try:
188
+ launched = open_url(value)
189
+ except Exception as error: # noqa: BLE001 - any failure is the same to us
190
+ launched = False
191
+ detail = str(error) or error.__class__.__name__
192
+ stderr("%s: could not launch a browser (%s). Open the URL above yourself.\n" % (name, detail))
193
+ else:
194
+ if not launched:
195
+ stderr("%s: could not launch a browser. Open the URL above yourself.\n" % name)
196
+
197
+ # Handing over the link is the job. Whether the page then loaded is not
198
+ # something this process can observe, so it is not claimed either way.
199
+ return EXIT_OK
200
+
201
+
202
+ def main(name: str, url, version: str) -> int:
203
+ return run(
204
+ argv=sys.argv[1:],
205
+ name=name,
206
+ version=version,
207
+ url=url,
208
+ # webbrowser builds the platform command itself and never goes through
209
+ # a shell, so a link carrying quotes or semicolons stays a link.
210
+ open_url=lambda target: webbrowser.open(target, new=2),
211
+ stdout=sys.stdout.write,
212
+ stderr=sys.stderr.write,
213
+ env=os.environ,
214
+ )