pycontrol-install 0.1.0a1__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.
@@ -0,0 +1,5 @@
1
+ """pyControl global installer."""
2
+
3
+ __version__ = "0.1.0a1"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,334 @@
1
+ """Command-line installer for the global pyControl app and its workspaces.
2
+
3
+ ``pycontrol-install`` installs ``pycontrol-gui`` once per machine with
4
+ ``uv tool install``, scaffolds plain-data workspace folders, and remembers the
5
+ workspace so the globally installed ``pycontrol-gui`` command opens it on the
6
+ next launch.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import os
13
+ import shlex
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import tomllib
18
+ from collections.abc import Sequence
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import Protocol
22
+
23
+ from pycontrol_install.shortcuts import create_shortcut
24
+
25
+ GUI_PACKAGE = "pycontrol-gui"
26
+ CORE_PACKAGE = "pycontrol-core"
27
+ _INTEGRATION_EXTRAS = ("notion", "s3", "slack")
28
+ _PRERELEASE_CHOICES = ("allow", "disallow", "if-necessary", "explicit", "if-necessary-or-explicit")
29
+
30
+
31
+ class BootstrapError(RuntimeError):
32
+ """Expected bootstrap failure suitable for a one-line CLI error."""
33
+
34
+
35
+ class CommandRunner(Protocol):
36
+ """Small abstraction for testing command construction without invoking uv."""
37
+
38
+ def has_command(self, name: str) -> bool: ...
39
+
40
+ def run(self, cwd: Path, command: Sequence[str]) -> None: ...
41
+
42
+ def run_capture(self, cwd: Path, command: Sequence[str]) -> str: ...
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class BootstrapConfig:
47
+ """Parsed bootstrap options."""
48
+
49
+ target: Path
50
+ launch: bool
51
+ force: bool
52
+ python: str
53
+ prerelease: str
54
+ index_url: str | None
55
+ extra_index_url: str | None
56
+ integration_extras: tuple[str, ...]
57
+ update_gui: bool
58
+ shortcut: bool | None
59
+
60
+
61
+ class SubprocessRunner:
62
+ """Run commands on the local machine."""
63
+
64
+ def has_command(self, name: str) -> bool:
65
+ return shutil.which(name) is not None
66
+
67
+ def run(self, cwd: Path, command: Sequence[str]) -> None:
68
+ print(f"Running in {cwd}: {shlex.join(command)}")
69
+ try:
70
+ subprocess.run(command, cwd=cwd, check=True)
71
+ except FileNotFoundError as exc:
72
+ raise BootstrapError(f"command not found: {command[0]}") from exc
73
+ except subprocess.CalledProcessError as exc:
74
+ raise BootstrapError(f"command failed with exit code {exc.returncode}: {shlex.join(command)}") from exc
75
+
76
+ def run_capture(self, cwd: Path, command: Sequence[str]) -> str:
77
+ try:
78
+ completed = subprocess.run(command, cwd=cwd, check=True, capture_output=True, text=True)
79
+ except FileNotFoundError as exc:
80
+ raise BootstrapError(f"command not found: {command[0]}") from exc
81
+ except subprocess.CalledProcessError as exc:
82
+ raise BootstrapError(f"command failed with exit code {exc.returncode}: {shlex.join(command)}") from exc
83
+ return completed.stdout.strip()
84
+
85
+
86
+ def build_parser() -> argparse.ArgumentParser:
87
+ parser = argparse.ArgumentParser(
88
+ prog="pycontrol-install",
89
+ description="Install the pyControl GUI globally with uv and create a workspace data folder.",
90
+ )
91
+ parser.add_argument(
92
+ "path",
93
+ nargs="?",
94
+ default=".",
95
+ help="Workspace path to create or initialize (default: current directory).",
96
+ )
97
+ parser.add_argument(
98
+ "--launch",
99
+ action="store_true",
100
+ help="Launch the pyControl GUI after the workspace is ready.",
101
+ )
102
+ parser.add_argument(
103
+ "--force",
104
+ action="store_true",
105
+ help="Pass --force to workspace scaffolding so differing bundled files are overwritten.",
106
+ )
107
+ parser.add_argument(
108
+ "--update-gui",
109
+ action="store_true",
110
+ help="Reinstall the newest pyControl GUI (preserving installed integration extras) and exit.",
111
+ )
112
+ parser.add_argument(
113
+ "--shortcut",
114
+ action=argparse.BooleanOptionalAction,
115
+ default=None,
116
+ help="Create (or skip) an OS shortcut for the pyControl GUI without prompting.",
117
+ )
118
+ parser.add_argument(
119
+ "--python",
120
+ default="3.12",
121
+ help="Python version request for the global tool environment (default: 3.12).",
122
+ )
123
+ parser.add_argument(
124
+ "--prerelease",
125
+ choices=_PRERELEASE_CHOICES,
126
+ default="if-necessary-or-explicit",
127
+ help=(
128
+ "uv prerelease resolution strategy (default: if-necessary-or-explicit, which selects pyControl "
129
+ "alphas because no stable release exists yet while keeping third-party dependencies stable)."
130
+ ),
131
+ )
132
+ parser.add_argument(
133
+ "--index-url",
134
+ default=None,
135
+ help="Package index URL for uv tool install, or PYCONTROL_INSTALL_INDEX_URL.",
136
+ )
137
+ parser.add_argument(
138
+ "--extra-index-url",
139
+ default=None,
140
+ help="Extra package index URL for uv tool install, or PYCONTROL_INSTALL_EXTRA_INDEX_URL.",
141
+ )
142
+ parser.add_argument(
143
+ "--with-slack",
144
+ action="store_true",
145
+ help="Install Slack notification dependencies into the global tool environment.",
146
+ )
147
+ parser.add_argument(
148
+ "--with-notion",
149
+ action="store_true",
150
+ help="Install Notion helper dependencies into the global tool environment.",
151
+ )
152
+ parser.add_argument(
153
+ "--with-s3",
154
+ action="store_true",
155
+ help="Install S3 export helper dependencies into the global tool environment.",
156
+ )
157
+ parser.add_argument(
158
+ "--with-integrations",
159
+ action="store_true",
160
+ help="Install all optional integration helper dependencies into the global tool environment.",
161
+ )
162
+ return parser
163
+
164
+
165
+ def config_from_args(args: argparse.Namespace) -> BootstrapConfig:
166
+ selected = set(_INTEGRATION_EXTRAS if args.with_integrations else ())
167
+ if args.with_notion:
168
+ selected.add("notion")
169
+ if args.with_s3:
170
+ selected.add("s3")
171
+ if args.with_slack:
172
+ selected.add("slack")
173
+
174
+ return BootstrapConfig(
175
+ target=Path(args.path),
176
+ launch=bool(args.launch),
177
+ force=bool(args.force),
178
+ python=str(args.python),
179
+ prerelease=str(args.prerelease),
180
+ index_url=args.index_url or os.environ.get("PYCONTROL_INSTALL_INDEX_URL") or None,
181
+ extra_index_url=args.extra_index_url or os.environ.get("PYCONTROL_INSTALL_EXTRA_INDEX_URL") or None,
182
+ integration_extras=tuple(extra for extra in _INTEGRATION_EXTRAS if extra in selected),
183
+ update_gui=bool(args.update_gui),
184
+ shortcut=args.shortcut,
185
+ )
186
+
187
+
188
+ def bootstrap(config: BootstrapConfig, runner: CommandRunner | None = None) -> Path | None:
189
+ """Install the GUI globally, then scaffold and remember the workspace.
190
+
191
+ Returns the workspace path, or ``None`` for ``--update-gui`` runs.
192
+ """
193
+ runner = runner or SubprocessRunner()
194
+ if not runner.has_command("uv"):
195
+ raise BootstrapError(
196
+ "uv is required because pycontrol-install uses uv; install it from "
197
+ "https://docs.astral.sh/uv/getting-started/installation/"
198
+ )
199
+
200
+ cwd = Path.cwd()
201
+ receipt = _receipt_path(runner, cwd)
202
+
203
+ if config.update_gui:
204
+ if receipt is None or not receipt.is_file():
205
+ raise BootstrapError(
206
+ f"{GUI_PACKAGE} is not installed as a uv tool yet; run pycontrol-install without --update-gui first."
207
+ )
208
+ extras = tuple(sorted({*_extras_from_receipt(receipt), *config.integration_extras}))
209
+ runner.run(cwd, _install_command(config, extras=extras, reinstall=True))
210
+ print(f"{GUI_PACKAGE} reinstalled at the newest version.")
211
+ return None
212
+
213
+ reinstall = receipt is not None and receipt.is_file()
214
+ runner.run(cwd, _install_command(config, extras=config.integration_extras, reinstall=reinstall))
215
+
216
+ bin_dir = Path(runner.run_capture(cwd, ["uv", "tool", "dir", "--bin"]))
217
+ target = _prepare_target(config.target)
218
+
219
+ workspace_init = [str(bin_dir / "pycontrol"), "workspace", "init", str(target)]
220
+ if config.force:
221
+ workspace_init.append("--force")
222
+ runner.run(target, workspace_init)
223
+
224
+ runner.run(target, [str(bin_dir / GUI_PACKAGE), "--set-workspace", str(target)])
225
+
226
+ if _shortcut_wanted(config.shortcut):
227
+ _create_shortcut_nonfatal(bin_dir)
228
+
229
+ if config.launch:
230
+ runner.run(target, [str(bin_dir / GUI_PACKAGE), str(target)])
231
+
232
+ return target
233
+
234
+
235
+ def main(argv: Sequence[str] | None = None) -> int:
236
+ parser = build_parser()
237
+ args = parser.parse_args(argv)
238
+ try:
239
+ workspace = bootstrap(config_from_args(args))
240
+ except BootstrapError as exc:
241
+ print(f"error: {exc}", file=sys.stderr)
242
+ return 2
243
+
244
+ if workspace is not None:
245
+ print(f"Workspace ready: {workspace}")
246
+ if not args.launch:
247
+ print(f"Launch anytime with: {GUI_PACKAGE}")
248
+ print(f"If the {GUI_PACKAGE} command is not found in new terminals, run: uv tool update-shell")
249
+ return 0
250
+
251
+
252
+ def _install_command(config: BootstrapConfig, *, extras: tuple[str, ...], reinstall: bool) -> list[str]:
253
+ # --refresh: resolve against a revalidated index so a release published
254
+ # moments ago is picked up instead of a cached older version.
255
+ # --force: claim the pycontrol/pycontrol-gui executables even when a stale
256
+ # shim or an older tool install already provides them.
257
+ command = [
258
+ "uv",
259
+ "tool",
260
+ "install",
261
+ "--refresh",
262
+ "--force",
263
+ "--python",
264
+ config.python,
265
+ "--prerelease",
266
+ config.prerelease,
267
+ ]
268
+ if config.index_url:
269
+ command.extend(["--index-url", config.index_url])
270
+ if config.extra_index_url:
271
+ command.extend(["--extra-index-url", config.extra_index_url])
272
+ if extras:
273
+ command.extend(["--with", f"{CORE_PACKAGE}[{','.join(sorted(extras))}]"])
274
+ command.extend(["--with-executables-from", CORE_PACKAGE])
275
+ if reinstall:
276
+ command.append("--reinstall")
277
+ command.append(GUI_PACKAGE)
278
+ return command
279
+
280
+
281
+ def _receipt_path(runner: CommandRunner, cwd: Path) -> Path | None:
282
+ try:
283
+ tool_dir = runner.run_capture(cwd, ["uv", "tool", "dir"])
284
+ except BootstrapError:
285
+ return None
286
+ if not tool_dir:
287
+ return None
288
+ return Path(tool_dir) / GUI_PACKAGE / "uv-receipt.toml"
289
+
290
+
291
+ def _extras_from_receipt(receipt: Path) -> tuple[str, ...]:
292
+ """Recover ``pycontrol-core`` extras from the uv tool receipt so an update
293
+ reinstall keeps previously chosen integrations."""
294
+ try:
295
+ data = tomllib.loads(receipt.read_text(encoding="utf-8"))
296
+ except (OSError, tomllib.TOMLDecodeError):
297
+ return ()
298
+ requirements = data.get("tool", {}).get("requirements", [])
299
+ for requirement in requirements:
300
+ if isinstance(requirement, dict) and requirement.get("name") == CORE_PACKAGE:
301
+ extras = requirement.get("extras", [])
302
+ return tuple(sorted(str(extra) for extra in extras if str(extra) in _INTEGRATION_EXTRAS))
303
+ return ()
304
+
305
+
306
+ def _prepare_target(raw_target: Path) -> Path:
307
+ target = raw_target.expanduser().resolve()
308
+ if target.exists() and not target.is_dir():
309
+ raise BootstrapError(f"target exists and is not a directory: {target}")
310
+ target.mkdir(parents=True, exist_ok=True)
311
+ return target
312
+
313
+
314
+ def _shortcut_wanted(shortcut: bool | None) -> bool:
315
+ if shortcut is not None:
316
+ return shortcut
317
+ if not sys.stdin.isatty():
318
+ return False
319
+ answer = input("Create an OS shortcut for the pyControl GUI? [Y/n] ").strip().lower()
320
+ return answer in ("", "y", "yes")
321
+
322
+
323
+ def _create_shortcut_nonfatal(bin_dir: Path) -> None:
324
+ try:
325
+ created = create_shortcut(bin_dir)
326
+ except Exception as exc: # noqa: BLE001 - shortcuts are a nicety, never fatal.
327
+ print(f"warning: could not create a shortcut: {exc}", file=sys.stderr)
328
+ return
329
+ if created is not None:
330
+ print(f"Shortcut created: {created}")
331
+
332
+
333
+ if __name__ == "__main__": # pragma: no cover
334
+ raise SystemExit(main())
@@ -0,0 +1,116 @@
1
+ """Optional OS shortcuts pointing at the globally installed ``pycontrol-gui``.
2
+
3
+ All variants target the stable uv tool shim (``<uv tool bin dir>/pycontrol-gui``),
4
+ which survives upgrades, so shortcuts never need regeneration.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import plistlib
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ APP_NAME = "pyControl GUI"
15
+ _BUNDLE_IDENTIFIER = "org.pycontrol.gui-launcher"
16
+
17
+
18
+ def create_shortcut(bin_dir: Path, home: Path | None = None, platform: str | None = None) -> Path | None:
19
+ """Create a double-clickable shortcut for the current OS.
20
+
21
+ Returns the created shortcut path, or ``None`` when the platform has no
22
+ shortcut support. Raises on failure; callers treat that as non-fatal.
23
+ """
24
+ home = home or Path.home()
25
+ platform = platform or sys.platform
26
+ if platform == "darwin":
27
+ return create_macos_app_bundle(bin_dir, home)
28
+ if platform == "win32":
29
+ return create_windows_start_menu_shortcut(bin_dir)
30
+ if platform.startswith("linux"):
31
+ return create_linux_desktop_entry(bin_dir, home)
32
+ return None
33
+
34
+
35
+ def create_macos_app_bundle(bin_dir: Path, home: Path) -> Path:
36
+ """Minimal ~/Applications app bundle wrapping the pycontrol-gui shim."""
37
+ bundle = home / "Applications" / f"{APP_NAME}.app"
38
+ macos_dir = bundle / "Contents" / "MacOS"
39
+ macos_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ info = {
42
+ "CFBundleName": APP_NAME,
43
+ "CFBundleDisplayName": APP_NAME,
44
+ "CFBundleIdentifier": _BUNDLE_IDENTIFIER,
45
+ "CFBundlePackageType": "APPL",
46
+ "CFBundleExecutable": "pycontrol-gui-launcher",
47
+ }
48
+ with (bundle / "Contents" / "Info.plist").open("wb") as stream:
49
+ plistlib.dump(info, stream)
50
+
51
+ launcher = macos_dir / "pycontrol-gui-launcher"
52
+ launcher.write_text(macos_launcher_script(bin_dir), encoding="utf-8", newline="\n")
53
+ launcher.chmod(launcher.stat().st_mode | 0o111)
54
+ return bundle
55
+
56
+
57
+ def macos_launcher_script(bin_dir: Path) -> str:
58
+ return f'#!/bin/sh\nexec "{bin_dir / "pycontrol-gui"}" "$@"\n'
59
+
60
+
61
+ def create_windows_start_menu_shortcut(bin_dir: Path) -> Path:
62
+ """Start-menu .lnk via the WScript.Shell COM object (no extra dependencies)."""
63
+ completed = subprocess.run(
64
+ ["powershell", "-NoProfile", "-Command", windows_shortcut_command(bin_dir)],
65
+ check=True,
66
+ capture_output=True,
67
+ text=True,
68
+ )
69
+ link_path = completed.stdout.strip().splitlines()[-1] if completed.stdout.strip() else ""
70
+ if not link_path:
71
+ raise RuntimeError("PowerShell did not report the created shortcut path")
72
+ return Path(link_path)
73
+
74
+
75
+ def windows_shortcut_command(bin_dir: Path) -> str:
76
+ target = bin_dir / "pycontrol-gui.exe"
77
+ return (
78
+ "$programs = [Environment]::GetFolderPath('Programs'); "
79
+ f"$link = Join-Path $programs '{APP_NAME}.lnk'; "
80
+ "$shell = New-Object -ComObject WScript.Shell; "
81
+ "$shortcut = $shell.CreateShortcut($link); "
82
+ f"$shortcut.TargetPath = '{target}'; "
83
+ "$shortcut.Save(); "
84
+ "Write-Output $link"
85
+ )
86
+
87
+
88
+ def create_linux_desktop_entry(bin_dir: Path, home: Path) -> Path:
89
+ applications = home / ".local" / "share" / "applications"
90
+ applications.mkdir(parents=True, exist_ok=True)
91
+ entry = applications / "pycontrol-gui.desktop"
92
+ entry.write_text(linux_desktop_entry(bin_dir), encoding="utf-8", newline="\n")
93
+ return entry
94
+
95
+
96
+ def linux_desktop_entry(bin_dir: Path) -> str:
97
+ return (
98
+ "[Desktop Entry]\n"
99
+ "Type=Application\n"
100
+ f"Name={APP_NAME}\n"
101
+ f'Exec="{bin_dir / "pycontrol-gui"}"\n'
102
+ "Terminal=false\n"
103
+ "Categories=Science;\n"
104
+ )
105
+
106
+
107
+ __all__ = [
108
+ "APP_NAME",
109
+ "create_linux_desktop_entry",
110
+ "create_macos_app_bundle",
111
+ "create_shortcut",
112
+ "create_windows_start_menu_shortcut",
113
+ "linux_desktop_entry",
114
+ "macos_launcher_script",
115
+ "windows_shortcut_command",
116
+ ]
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycontrol-install
3
+ Version: 0.1.0a1
4
+ Summary: uvx installer for the global pyControl GUI and its workspaces.
5
+ Project-URL: Homepage, https://github.com/karpova-lab/pycontrol-install
6
+ Author: pyControl contributors
7
+ License: GPL-3.0-or-later
8
+ License-File: LICENSE.txt
9
+ Keywords: behaviour,neuroscience,pycontrol,uv
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+
19
+ # pycontrol-install
20
+
21
+ `pycontrol-install` is a small `uvx` installer for pyControl. It installs
22
+ `pycontrol-gui` (and the `pycontrol` CLI) **once per machine** with
23
+ `uv tool install`, scaffolds plain-data workspace folders, and remembers the
24
+ workspace so the global `pycontrol-gui` command opens it on the next launch.
25
+
26
+ Install `uv`, then install pyControl and create a workspace:
27
+
28
+ ```bash
29
+ uvx pycontrol-install my-workspace --launch
30
+ ```
31
+
32
+ This:
33
+
34
+ 1. runs `uv tool install pycontrol-gui` into a self-contained, uv-managed
35
+ environment, putting `pycontrol-gui` and `pycontrol` on your PATH,
36
+ 2. scaffolds the `my-workspace` data folder (no `pyproject.toml`, `.venv`, or
37
+ lockfile — workspaces are pure data),
38
+ 3. remembers it as the GUI's current workspace,
39
+ 4. offers to create an OS shortcut (macOS `~/Applications`, Windows Start
40
+ menu, Linux application menu), and
41
+ 5. launches the GUI (`--launch`).
42
+
43
+ After setup, launch the GUI from anywhere with `pycontrol-gui`.
44
+
45
+ To initialize the current directory instead:
46
+
47
+ ```bash
48
+ uvx pycontrol-install . --launch
49
+ ```
50
+
51
+ This package intentionally uses `uv`. Users who prefer pip can still install
52
+ and run pyControl manually from a Python 3.11+ virtual environment; see the
53
+ `pycontrol-core` getting-started guide for the pip/venv workflow.
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ pycontrol-install [path] [--launch] [--force] [--shortcut | --no-shortcut] [--update-gui]
59
+ ```
60
+
61
+ `path` defaults to the current directory. Relative paths are resolved from the
62
+ directory where the command is run. Re-running `pycontrol-install` is safe: the
63
+ global install is refreshed (`--reinstall`) and workspace scaffolding is a safe
64
+ merge.
65
+
66
+ Examples:
67
+
68
+ ```bash
69
+ pycontrol-install
70
+ pycontrol-install my-workspace --launch
71
+ pycontrol-install ~/pycontrol-workspaces/ws1 --with-slack
72
+ pycontrol-install my-workspace --no-shortcut
73
+ ```
74
+
75
+ ## Updating the GUI
76
+
77
+ ```bash
78
+ uvx pycontrol-install --update-gui
79
+ ```
80
+
81
+ Reinstalls the newest `pycontrol-gui` against a freshly refreshed package
82
+ index, preserving integration extras recorded in the uv tool receipt.
83
+ (`uv tool upgrade pycontrol-gui` also works; `--update-gui` additionally
84
+ guarantees the index is revalidated and the pyControl resolution strategy is
85
+ applied.)
86
+
87
+ ## Integration helpers
88
+
89
+ Optional integration dependencies are installed into the global tool
90
+ environment:
91
+
92
+ ```bash
93
+ pycontrol-install my-workspace --with-slack
94
+ pycontrol-install my-workspace --with-notion
95
+ pycontrol-install my-workspace --with-s3
96
+ pycontrol-install my-workspace --with-integrations
97
+ ```
98
+
99
+ To add an integration to an existing install without touching any workspace,
100
+ combine it with `--update-gui`:
101
+
102
+ ```bash
103
+ uvx pycontrol-install --update-gui --with-slack
104
+ ```
105
+
106
+ ## Release smoke tests
107
+
108
+ Package indexes can be overridden:
109
+
110
+ ```bash
111
+ pycontrol-install test-workspace \
112
+ --index-url https://test.pypi.org/simple/ \
113
+ --extra-index-url https://pypi.org/simple/
114
+ ```
115
+
116
+ ## Development
117
+
118
+ ```bash
119
+ uv sync --group dev
120
+ uv run pytest
121
+ uv run ruff check src tests
122
+ ```
@@ -0,0 +1,8 @@
1
+ pycontrol_install/__init__.py,sha256=cAUQjIarvq-QPQrG7Zxt6AWRdXVXG2jKz34jpBZbAXg,86
2
+ pycontrol_install/cli.py,sha256=pPDPKXU86kv0R6-JOPX0i-1knrroxLhostjr1OB8QS0,11787
3
+ pycontrol_install/shortcuts.py,sha256=RqWUBtPbdXt33c-kZRH67U3mk-gx1PoVDN8GiC1B67A,3925
4
+ pycontrol_install-0.1.0a1.dist-info/METADATA,sha256=SfTF2leLzJaGa2ZGOTy3XapMtLJZF_8z5QoldgROVwk,3686
5
+ pycontrol_install-0.1.0a1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
6
+ pycontrol_install-0.1.0a1.dist-info/entry_points.txt,sha256=Ijc_DZaKwhox1CJLjnD30GnM57_Eu7mduHQ473GL0V0,65
7
+ pycontrol_install-0.1.0a1.dist-info/licenses/LICENSE.txt,sha256=eOoHjIIzecTuMO3xRRPSEAWfIBYJquFE5X7iS3NJYNk,1453
8
+ pycontrol_install-0.1.0a1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pycontrol-install = pycontrol_install.cli:main
@@ -0,0 +1,8 @@
1
+ Copyright © 2026 Howard Hughes Medical Institute
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7
+ Neither the name of HHMI nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.