devspace 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.
devspace/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """devspace package."""
2
+
3
+ __all__ = ["__version__"]
4
+ __version__ = "0.1.0"
devspace/cli.py ADDED
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from .config import create_space, load_project_config
9
+ from .orchestrator import launch_workspace, quick_run, run_session_launch, status_ribbon
10
+
11
+
12
+ def main(argv: list[str] | None = None) -> int:
13
+ parser = argparse.ArgumentParser(
14
+ prog="devspace",
15
+ description="Create and manage devspace project workspaces.",
16
+ )
17
+ subparsers = parser.add_subparsers(dest="command")
18
+
19
+ create_parser = subparsers.add_parser("create", help="Create a new devspace")
20
+ create_parser.add_argument("name", help="Name of the devspace to create")
21
+
22
+ load_parser = subparsers.add_parser("load", help="Load a .devspace.toml file")
23
+ load_parser.add_argument("project", nargs="?", default=".", help="Project directory or space name to inspect")
24
+
25
+ launch_parser = subparsers.add_parser("launch", help="Launch the configured workspace sessions")
26
+ launch_parser.add_argument("project", nargs="?", default=".", help="Project directory or space name")
27
+
28
+ status_parser = subparsers.add_parser("status", help="Open the lightweight status ribbon")
29
+ status_parser.add_argument("project", nargs="?", default=".", help="Project directory or space name")
30
+
31
+ quick_parser = subparsers.add_parser("quick-run", help="Run a quick project command from the config")
32
+ quick_parser.add_argument("project", nargs="?", default=".", help="Project directory or space name")
33
+ quick_parser.add_argument("index", nargs="?", default="0", help="Zero-based index of the quick run command")
34
+
35
+ args = parser.parse_args(argv)
36
+
37
+ if args.command == "create":
38
+ project_dir = create_space(args.name)
39
+ print(f"Created devspace '{args.name}' at {project_dir}")
40
+ return 0
41
+
42
+ if args.command == "load":
43
+ project = Path(args.project)
44
+ try:
45
+ config = load_project_config(project)
46
+ except FileNotFoundError as exc:
47
+ print(f"Error: {exc}", file=sys.stderr)
48
+ return 1
49
+ print(json.dumps(config, indent=2, sort_keys=True))
50
+ return 0
51
+
52
+ if args.command == "launch":
53
+ project = Path(args.project)
54
+ try:
55
+ config = load_project_config(project)
56
+ except FileNotFoundError as exc:
57
+ print(f"Error: {exc}", file=sys.stderr)
58
+ return 1
59
+ plan = launch_workspace(project, config)
60
+ run_session_launch(project, config)
61
+ print(json.dumps(plan, indent=2, sort_keys=True))
62
+ return 0
63
+
64
+ if args.command == "status":
65
+ project = Path(args.project)
66
+ try:
67
+ config = load_project_config(project)
68
+ except FileNotFoundError as exc:
69
+ print(f"Error: {exc}", file=sys.stderr)
70
+ return 1
71
+ status_ribbon(project, config)
72
+ return 0
73
+
74
+ if args.command == "quick-run":
75
+ project = Path(args.project)
76
+ try:
77
+ config = load_project_config(project)
78
+ except FileNotFoundError as exc:
79
+ print(f"Error: {exc}", file=sys.stderr)
80
+ return 1
81
+ return quick_run(project, config, int(args.index))
82
+
83
+ parser.print_help()
84
+ return 0
85
+
86
+
87
+ if __name__ == "__main__":
88
+ raise SystemExit(main())
devspace/config.py ADDED
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+ import tomllib
7
+
8
+
9
+ def create_space(name: str, base_dir: str | Path = ".") -> Path:
10
+ """Create a new devspace directory with a starter .devspace.toml config."""
11
+ project_dir = Path(base_dir) / name
12
+ project_dir.mkdir(parents=True, exist_ok=False)
13
+
14
+ commands = [
15
+ "pwd",
16
+ "echo 'devspace ready'",
17
+ ]
18
+ browser_targets = [
19
+ "https://example.com",
20
+ ]
21
+ config = f"""
22
+ [project]
23
+ name = "{name}"
24
+ root = "."
25
+ venv = ".venv"
26
+
27
+ [windows]
28
+ count = 2
29
+ layout = "grid"
30
+ commands = {json.dumps(commands)}
31
+
32
+ [open]
33
+ urls = {json.dumps(browser_targets)}
34
+ terminals = ["bash -lc 'pwd'", "bash -lc 'echo devspace ready'"]
35
+
36
+ [[open.actions]]
37
+ type = "terminal"
38
+ command = "bash -lc 'pwd'"
39
+
40
+ [[open.actions]]
41
+ type = "browser"
42
+ url = "https://example.com"
43
+
44
+ [[open.actions]]
45
+ type = "command"
46
+ command = "bash -lc 'echo devspace ready'"
47
+
48
+ [status]
49
+ ports = ["8000", "8080"]
50
+
51
+ [quickrun]
52
+ commands = ["pytest -q", "python -m http.server 8000"]
53
+ """.strip()
54
+
55
+ config_path = project_dir / ".devspace.toml"
56
+ config_path.write_text(config + "\n", encoding="utf-8")
57
+ return project_dir
58
+
59
+
60
+ def resolve_project_path(project_arg: str | Path) -> Path:
61
+ """Resolve a devspace project path from a name or file path."""
62
+ project_path = Path(project_arg)
63
+ if project_path.is_dir() and (project_path / ".devspace.toml").exists():
64
+ return project_path
65
+ if project_path.name == ".devspace.toml":
66
+ return project_path.parent
67
+ if project_path.suffix == ".toml":
68
+ return project_path.parent
69
+ if project_path.exists() and project_path.is_dir():
70
+ return project_path
71
+ return project_path
72
+
73
+
74
+ def load_project_config(project_dir: str | Path = ".") -> dict[str, Any]:
75
+ """Load a project's .devspace.toml configuration file."""
76
+ project_path = resolve_project_path(project_dir)
77
+ config_path = project_path if project_path.name == ".devspace.toml" else project_path / ".devspace.toml"
78
+
79
+ if not config_path.exists():
80
+ raise FileNotFoundError(f"No .devspace.toml file found in {project_path}")
81
+
82
+ with config_path.open("rb") as handle:
83
+ return tomllib.load(handle)
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import socket
6
+ import subprocess
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ def _district_env(project_dir: Path, config: dict[str, Any]) -> dict[str, str]:
12
+ env = os.environ.copy()
13
+ env["DEVSPACE_PROJECT_ROOT"] = str(project_dir.resolve())
14
+ env["DEVSPACE_PROJECT_NAME"] = str(config.get("project", {}).get("name", project_dir.name))
15
+ env["DEVSPACE_VENV"] = str(config.get("project", {}).get("venv", ".venv"))
16
+ return env
17
+
18
+
19
+ def _open_linux_url(url: str) -> None:
20
+ candidates = [
21
+ ["xdg-open", url],
22
+ ["x-www-browser", url],
23
+ ["www-browser", url],
24
+ ["sensible-browser", url],
25
+ ["/usr/bin/open", url],
26
+ ]
27
+ browser = os.environ.get("BROWSER")
28
+ if browser:
29
+ candidates.insert(0, [browser, url])
30
+
31
+ for command in candidates:
32
+ if shutil.which(command[0]) is None:
33
+ continue
34
+ subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
35
+ return
36
+
37
+ python_bin = shutil.which("python3") or shutil.which("python")
38
+ if python_bin:
39
+ subprocess.Popen([python_bin, "-m", "webbrowser", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
40
+
41
+
42
+ def _port_is_open(port: str | int) -> bool:
43
+ port_int = int(port)
44
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
45
+ sock.settimeout(0.5)
46
+ return sock.connect_ex(("127.0.0.1", port_int)) == 0
47
+
48
+
49
+ def _run_open_action(project_dir: Path, env: dict[str, str], action: dict[str, Any]) -> None:
50
+ action_type = str(action.get("type", "command")).lower()
51
+ if action_type == "browser":
52
+ url = action.get("url") or action.get("target")
53
+ if url:
54
+ _open_linux_url(str(url))
55
+ return
56
+
57
+ if action_type == "terminal":
58
+ command = action.get("command") or action.get("cmd") or "bash"
59
+ subprocess.Popen(["bash", "-lc", str(command)], cwd=str(project_dir), env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
60
+ return
61
+
62
+ command = action.get("command") or action.get("cmd") or action.get("shell")
63
+ if command:
64
+ subprocess.Popen(["bash", "-lc", str(command)], cwd=str(project_dir), env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
65
+
66
+
67
+ def launch_workspace(project_dir: str | Path, config: dict[str, Any]) -> dict[str, Any]:
68
+ """Create a lightweight workspace launch plan from the config."""
69
+ project_path = Path(project_dir)
70
+ windows = int(config.get("windows", {}).get("count", 1))
71
+ commands = config.get("windows", {}).get("commands", [])
72
+ if not commands:
73
+ commands = ["pwd"] * max(windows, 1)
74
+ env = _district_env(project_path, config)
75
+
76
+ session = []
77
+ for idx in range(max(windows, 1)):
78
+ cmd = commands[idx] if idx < len(commands) else "pwd"
79
+ session.append({
80
+ "index": idx,
81
+ "command": cmd,
82
+ "cwd": str(project_path),
83
+ "env": env,
84
+ })
85
+
86
+ opens = config.get("open", {})
87
+ urls = opens.get("urls", [])
88
+ terminals = opens.get("terminals", [])
89
+ explicit_actions = opens.get("actions", [])
90
+ plan = {
91
+ "project": project_path.name,
92
+ "windows": windows,
93
+ "sessions": session,
94
+ "status": "ready",
95
+ "opens": {
96
+ "urls": urls,
97
+ "terminals": terminals,
98
+ "actions": explicit_actions,
99
+ },
100
+ }
101
+
102
+ for url in urls:
103
+ _open_linux_url(url)
104
+ for terminal in terminals:
105
+ subprocess.Popen(["bash", "-lc", terminal], cwd=str(project_path), env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
106
+ for action in explicit_actions:
107
+ _run_open_action(project_path, env, action)
108
+
109
+ return plan
110
+
111
+
112
+ def quick_run(project_dir: str | Path, config: dict[str, Any], index: int = 0) -> int:
113
+ """Execute a quick command configured in the project."""
114
+ project_path = Path(project_dir)
115
+ commands = config.get("quickrun", {}).get("commands", [])
116
+ if not commands:
117
+ print("No quickrun commands are configured for this space.")
118
+ return 1
119
+ if index < 0 or index >= len(commands):
120
+ print(f"Quick-run index {index} out of range for {len(commands)} commands.")
121
+ return 1
122
+
123
+ cmd = commands[index]
124
+ print(f"Running: {cmd}")
125
+ completed = subprocess.run(
126
+ cmd,
127
+ shell=True,
128
+ cwd=str(project_path),
129
+ env=_district_env(project_path, config),
130
+ executable="/bin/bash",
131
+ )
132
+ return completed.returncode
133
+
134
+
135
+ def status_ribbon(project_dir: str | Path, config: dict[str, Any]) -> None:
136
+ """Print a lightweight status ribbon that mimics a desktop panel."""
137
+ project_path = Path(project_dir)
138
+ ports = config.get("status", {}).get("ports", [])
139
+ port_status = []
140
+ for port in ports:
141
+ port_status.append(f"{port}: {'open' if _port_is_open(port) else 'down'}")
142
+ print(f"Project: {config.get('project', {}).get('name', project_path.name)}")
143
+ print("Active context: ready")
144
+ print(f"Ports: {', '.join(port_status) if port_status else 'none configured'}")
145
+ print("Scratchpad: ready")
146
+
147
+
148
+ def run_session_launch(project_dir: str | Path, config: dict[str, Any]) -> None:
149
+ """Run the configured launch commands in terminal sessions."""
150
+ project_path = Path(project_dir)
151
+ plan = launch_workspace(project_path, config)
152
+ for session in plan["sessions"]:
153
+ subprocess.Popen(
154
+ ["/bin/bash", "-lc", session["command"]],
155
+ cwd=session["cwd"],
156
+ env=session["env"],
157
+ stdout=subprocess.DEVNULL,
158
+ stderr=subprocess.DEVNULL,
159
+ start_new_session=True,
160
+ )
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: devspace
3
+ Version: 0.1.0
4
+ Summary: A clean workflow and context orchestrator for minimalist Linux setups.
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+
8
+ # devspace
9
+
10
+ Core Features to Build1. Context Orchestration (devspace load <project>)When you fire up a project workspace, the Python script reads a .devspace.toml file from your project root and uses wmctrl to:Spin up a designated number of terminal windows arranged exactly how you like them.Automatically activate your project’s specific Python venv in those terminals.Open your project's local documentation or API references in a lightweight X11 browser (like surf or luakit).Launch your background mock databases or docker containers.2. The Persistent "Status Ribbon" (The GUI Element)Instead of a huge dashboard, it generates a tiny, elegant, borderless X11 window using tkinter that docks right into your Fluxbox setup (acting like a secondary panel). It display blocks like:Active Project Context: How long you've been working on the current session.Local Micro-Status: Are your local development ports (e.g., localhost:8000) alive or throwing 500 errors?Scratchpad Dropdown: A clean, markdown-supported text box that drops down via a global hotkey to jot down notes without opening a heavy text editor.3. Ephemeral Quick-RunnerA search-bar style pop-up launcher (similar to Rofi, but built tailored to developers) that indexes only your project-specific actions. Pressing Ctrl + Space brings up a fast, modern input field where you can type project shortcuts (e.g., run tests, deploy staging, tail logs) defined entirely in your Python package config.How to Structure and Build ItSince you are running inside a venv on a minimal Debian installation, keep the library dependencies incredibly lean to preserve that lightning-fast Fluxbox feel.Recommended PyPI Stack:pyyaml or standard tomli (built-in for Python 3.11+ on Debian 12) — For reading configurations. [1] (/goto?url=CAESZgHrOzAVc1CuWOZEerWcsyLbIQ4xAc5OzLZy-x_xhIdkLz9lH-_iKIXDaiOu2dgKvFA_E4ieHUojoxzuFneBUUqoE6oz5a6EPSUOCjO1tHVovktc5q9Q40FFbkfB3ahORHpuT8VZrQ)Textual — If you decide to go the terminal UI route; it looks gorgeous, behaves like a modern GUI, but runs completely inside your terminal emulator.customtkinter — If you choose a GUI route; it upgrades standard Tkinter with gorgeous dark/light mode widgets without requiring heavy Qt or GTK packages.python-xlib or sh — For executing shell bindings safely to arrange window states.Example pyproject.toml layout:toml[build-system]
11
+ requires = ["setuptools>=61.0.0"]
12
+ build-backend = "setuptools.build_meta"
13
+
14
+ [project]
15
+ name = "devspace"
16
+ version = "0.1.0"
17
+ description = "A clean workflow and context orchestrator for minimalist Linux setups."
18
+ dependencies = [
19
+ "customtkinter>=5.2.0",
20
+ "sh>=2.0.0"
21
+ ]
22
+
23
+ [project.scripts]
24
+ devspace = "devspace.cli:main"
25
+ Use code with caution.Why this will be ImpressiveIt moves away from standard fluff projects and directly solves a universal developer friction point: environment setup and switching fatigue. Showing off a tool that handles window management, local port monitoring, and isolated project execution on a clean, snappy Linux architecture demonstrates a strong grasp of OS-level automation, config parsing, and clean desktop architecture.
@@ -0,0 +1,9 @@
1
+ devspace/__init__.py,sha256=z64AcJqso1ZBwsdlLC6brIxqH7iiA6sX8tnoUWWvxNU,73
2
+ devspace/cli.py,sha256=bQIisEA3iMkZsNSQdmQKfqPgYZcN7KAOIthwoLMHiDY,3231
3
+ devspace/config.py,sha256=bteQgLP1v_TVKeoDeHM3Z7oo94rLFyHTxWs6oqiCdmA,2216
4
+ devspace/orchestrator.py,sha256=W7fBX1b8BVSfBTA3oytOfxnycHOTlPYOx1B44pQRZ70,5725
5
+ devspace-0.1.0.dist-info/METADATA,sha256=1ECHWmCMc8mBWS8B-ZTVibTzUvY5QCIT4gpoLJnMXf4,3321
6
+ devspace-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
7
+ devspace-0.1.0.dist-info/entry_points.txt,sha256=6Dt6_HfPx68Ebb_GLYnQq4_vxsedhIGfFFfbQIr4oEg,47
8
+ devspace-0.1.0.dist-info/top_level.txt,sha256=oBiP-5GGc9nTTqiWDai2U7caMg81SvF11-adGLYgwa0,9
9
+ devspace-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devspace = devspace.cli:main
@@ -0,0 +1 @@
1
+ devspace